Oauth 2.0 and Unity

Started by
20 comments, last by haptixgames 8 years, 4 months ago
There may be an OAuth flow to login by entering your credentials in your own app and then use nothing but REST requests to the authentication server. That would be ideal. I've never seen such a flow yet though...

I would be surprised if EVERYONE where I work didn't know about such a flow, because it would simplify our apps tremendously.
Advertisement

With a WWW object it is pretty easy to handle web requests. You can either wait on your current thread or launch a coroutine to do the job. A quick search gives this great little example. No need to run a listener or anything. The only thing I would change is instead of testing against null, using IsNullOrEmpty on the error string.

No need for a browser window, no need to use a listener of any kind. Just fire out the request, yield on it, and eventually get the results. You can also use .progress to see how far along it is, in the case of transfers you know will be large.

Well there is a need for a browser window for OAuth as they have to Allow or Deny your request at least once before you can get the data.

Oops, sorry, I was thinking of standalone windows forms apps and Metro apps when I said that, not Windows desktop apps that use Unity. To take complete control over the web browser widget, you have to be a Windows app that has access to one of the programmatically controllable web widgets. Unity doesn't include one of those as far as I know.


With a Unity app, I've never done "real" OAuth authentication on a PC. The hack we use when running our game in the Unity editor is to start an HttpListener locally, use Application.OpenURL to launch a browser window with the OAuth login URL, and have the redirect_uri go to localhost on whatever port you're listening on. The HttpListener then catches the response. This would probably not be an acceptable solution to ship in your game, though.

That might actually still be an ok solution as the game doesn't need to poll all the time for the data changes (or at least I don't think I'm going to as it's likely that you will only update it when you start or when you force an update) The data from Fitbit is just going to be used for in game values so I won't need live updates really.

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+

Well I got it to the point of getting the auth_token out of the callback now.

Had to use a plugin with a webview.

Next part is I can't quite figure out how to form my POST to Fitbit to my full access_token


Authorization Header
The Authorization header should be set to Basic followed by a space and a Base64 encoded string of your application’s client id and secret concatenated with a colon.
Body Parameters
code required	The authorization code received in the callback as a URI parameter
Type: string
grant_type required	authorization_code
Type: string
client_id	This is your Fitbit API application id from your settings on [dev.fitbit.com](https://dev.fitbit.com/apps)
Type: string
redirect_uri	Required if specified in the redirect to the authorization page. Must be exact match.
Type: URI
URI Parameter
Description
Example
This example assumes that the code URI parameter value in the callback URI was 1234567890.
POST https://api.fitbit.com/oauth2/token
Authorization: Basic Y2xpZW50X2lkOmNsaWVudCBzZWNyZXQ=
Content-Type: application/x-www-form-urlencoded

client_id=22942C&grant_type=authorization_code&redirect_uri=http%3A%2F%2Fexample.com%2Fcallback&code=1234567890
Example response:
{ "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzAzNDM3MzUsInNjb3BlcyI6Indwcm8gd2xvYyB3bnV0IHdzbGUgd3NldCB3aHIgd3dlaSB3YWN0IHdzb2MiLCJzdWIiOiJBQkNERUYiLCJhdWQiOiJJSktMTU4iLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJpYXQiOjE0MzAzNDAxMzV9.z0VHrIEzjsBnjiNMBey6wtu26yHTnSWz_qlqoEpUlpc", "expires_in": 600, "refresh_token": "c643a63c072f0f05478e9d18b991db80ef6061e4f8e6c822d83fed53e5fafdd7", "token_type": "Bearer" }
M

Is what their documentation says. Does anyone know how I can do the request? Like what format I should use?

I've tried doing the WWW class in Unity but it never seems to work (or at least nothing happens in my app like it should to alert me that it worked) I unfortunately can't hook up my phone to the computer to do remote debugging as my computer does not detect my nexus 4 which is highly frustrating..

Would love some help getting this out of the way cause I just...wanna make a game damn it..


private void DidReceiveMessageEvent(WebView _webview, WebViewMessage _message)
        {
            Debug.Log("Received Did Receive Message Event");
            Debug.Log("Message= " + _message);
            WebView.GetComponent<WebView>().Hide();
            if(_message.Arguments.ContainsKey("code"))
            {
                PassMessage(_message);
            }
            else
            {
                _data = _message.ToString();
                /*_accessToken = _message.Arguments["access_token"];
                Dictionary<string, string> headers = new Dictionary<string, string>();
                headers.Add("Authorization:"," Bearer "+ _accessToken);
                WWW www = new WWW("https://api.fitbit.com/1/user/-/profile.json",null,headers);
                StartCoroutine(WaitForRequest(www));*/
            }
            _statusMessage = "end of receive message event";

        }
 
         IEnumerator WaitForAccess(WWW www)
         {
             _statusMessage = "waiting for access";
             yield return www;
 
             // check for errors
             if (www.error == null)
             {
                 _statusMessage = "no error";
                 //Debug.Log("WWW Ok!: " + www.text);
                 _accessToken = www.text;
             } else
             {
                 _statusMessage = "error";
                 //Debug.Log("WWW Error: "+ www.error);
                 _accessToken = "error!";
             }    
         } 

        private void PassMessage(WebViewMessage message)
        {

            //We get the message to grab the code from it and while testing, display it since 
            //we can't check this in the editor.
            _returnCode = message.Arguments["code"];

            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_clientID + ";" + _consumerSecret);
            var encoded = Convert.ToBase64String(plainTextBytes);
            //make the content
            var content = "client_id=" + _clientID + "&grant_type=authorization_code&redirect_uri=" + CallBackUrl +
                          "callback&code=" + _returnCode;
            var url = new URL("https://api.fitbit.com/oauth2/token");
            //get the size in bytes
            byte[] bytes = new byte[content.Length * sizeof(char)];
            Buffer.BlockCopy(content.ToCharArray(), 0, bytes, 0, bytes.Length);

            // next we will dothe authorization part of the OAuth2
            var form = new Dictionary<string,string>();
            form.Add("Authorization:", "Basic " + encoded);
            form.Add("Content-Type:", "application/x-www-form-urlEncoded");
            form.Add("Content: ",content);
            form.Add("Content-Length:", bytes.Length.ToString());

            var response = new Dictionary<string, string>();
            _statusMessage = "starting www";
            WWW www = new WWW(url.URLString,bytes,form);
            WaitForAccess(www);

Code I've tried that hasn't worked so far...

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+
The first thing I notice is that you're calling WaitForAccess directly, yet the function contains yields (in Unity terminology, it's a coroutine). Calling coroutines directly doesn't work. You need to call StartCoroutine(WaitForAccess(www)) or something similar to execute it properly. This should at least let you get a response from the web service. Whether it'll work or not, I can't tell at a glance.

You should use Encoding.ASCII.GetBytes(string) instead of making a byte[] and calling Buffer.BlockCopy.

You don't need to use the 'new URL' part. Just pass that URL string literal directly in the WWW constructor.

You might try using a WWWForm instead of the plain Dictionary as well. It looks like it's more convenient and might help eliminate typos more easily.

The URL part is from a plugin that I'm using since I was trying to use a utility from there but they don't support headers.

I get a 400 bad request now when I make the POST

I tried with and without the commented lines in this code snippet


var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_clientID + ";" + _consumerSecret);
            var encoded = Convert.ToBase64String(plainTextBytes);
            //make the content
            var content = "client_id=" + _clientID + "&grant_type=authorization_code&redirect_uri=" + CallBackUrl +
                          "callback&code=" + _returnCode;
            
            //get the size in bytes

            var bytes = Encoding.ASCII.GetBytes(content);

            // next we will dothe authorization part of the OAuth2
            var form = new WWWForm();
            form.AddField("Authorization:", "Basic " + encoded);
            form.AddField("Content-Type:", "application/x-www-form-urlEncoded");
            //form.AddField("Content: ", content);
            //form.AddField("Content-Length:", bytes.Length.ToString());

            _statusMessage += "starting www /n";
            bWWWRequest = true;

            _wwwRequest = new WWW("https://api.fitbit.com/oauth2/token", form.data, form.headers);
            StartCoroutine(WaitForAccess(_wwwRequest));

I really feel like I'm just missing something basic for doing a POST call or something...or maybe it's just the order or I have no idea at this point...

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+
http://docs.unity3d.com/ScriptReference/WWWForm-headers.html

From that example, it looks like you need to remove the colon from Authorization, since Unity seems to take the key/value pairs and adds the colon itself.

Also, instead of constructing your URLEncoded content string by hand, see if calling form.AddField on each individual field will do it for you. I suspect it will.

Yeah I never thought of the colon issue, removed now and still no dice.

Not quite sure what you mean by calling addField on each individual field. You mean the pieces of the url that I have make there?

The documentation has it all as one string so I'm reasonably sure that adding them all as separate fields won't work...unless it's like += which might work I guess but it would still have to be encoded anyways

EDIT:

Been talking to a buddy and he explained how the adding would go like you said. Still not working correctly but hopefully we're gettin closer...

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+

Well after havin a buddy try to help me out today, I think it might actually be down to Fitbit being messed up since I keep getting Redirect_uri mismatch even though my uri is the exact same as in the api..guess gotta wait till they get back to me...(saw another on their forums that has the same problem since yesterday)

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+

Got it last night!

So apparently for fitbit (can't attest to other OAuth2 stuff) they have an EscapedURI in their example Doc but it had to be UNEscaped ...Rather frustrating. I was returning an Escaped URL as it was asking for it for the initial url creation and in the doc it looked escaped as well.

My final code for the Auth part now looks like this.


            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_clientID + ":" + _consumerSecret);
            var encoded = Convert.ToBase64String(plainTextBytes);

            var form = new WWWForm();
            form.AddField("client_id", _clientID);
            form.AddField("grant_type", "authorization_code");
            form.AddField("redirect_uri", WWW.UnEscapeURL(CallBackUrl));
            form.AddField("code", _returnCode);

            var headers = form.headers;
            headers["Authorization"] = "Basic " + encoded;

            _wwwRequest = new WWW(_tokenURL, form.data, headers);

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+

Wrote a post about it and the stuff I did to get it done.

http://technicalartistry.blogspot.be/2015/07/oauth2-unity-and-month-of-cursing.html

EDIT:

For those that are finding this post in the future, if you're trying to do it with Fitbit OR just want to not have to buy a plugin from the app store,

http://www.gamedev.net/topic/668470-oauth-20-and-unity/page-2#entry5270815

I got another post that will fix it and make it all work with the native android app (no idea on iOS as I haven't had to dev for it yet.)

Portfolio

"I got a Masters in Life Fuckin' "

TheChubu+

This topic is closed to new replies.

Advertisement