Serverless authentication with Instagram

In this post I will explain how to implement a Unity app for Android which allows the user to authenticate using an Instagram account – without a server (i.e., directly against Instagram’s server.) In the process we’ll also learn how to write an Android plugin.

Overview

Instagram supports client-side authentication (OAuth implicit flow, a.k.a. 2-legged OAuth). This flow enables an app to obtain a user’s OAuth credentials directly: the access token is delivered to the app via the response URI. However, the flow was designed with JavaScript applications in mind, as the app is expected to have access to the response URI (“Simply grab the access_token off the URL fragment and you’re good to go.”)  Alas, this is a challenge for a mobile app, as the app needs to relinquish control to the browser when directing the user to authenticate. How then can the app regain control and obtain the response URI with the access token?

The solution is to configure a redirect URI with a custom scheme (instead of https). An intent filter will be added to the app to handle this custom scheme. This will result in the browser directing the flow back to the app when the response URI arrives. Adding an intent filter to an Android Unity app is seemingly a simple task, but it has its share of pit falls and online examples are scarce. Full instructions ahead.

Flow

The chronological order of events in runtime (once everything is already set up and running):

  1. A user opens the app manually
  2. The user chooses to sign in. The app redirects the user to a browser to sign in on Instagram’s page
  3. Upon successful authentication Instagram redirects the browser to a URI with a custom scheme
  4. The Android OS opens the app automatically to handle this URI
  5. The plugin (Java) code is called, which parses the URI and then hands over the data to a script in the Unity project

Tools

I’m using Windows 7. The tools I used for the job:

  1. Unity Android Pro v4.1.3
  2. Android SDK v21
  3. Eclipse 4.2.1 + ADT v22

For help with installation see my previous post.

Note: The Unity scripts in this example are written in Boo. There’s about a 98% chance you’re writing in C# or UnityScript, but I’m sure you’ll find no problem understanding the scripts – they’re quite minimal.

Register Your Application with Instagram

First you will need an Instagram account. Then you should register your app.

Configure the Redirect URI using a custom scheme.
For example: mycustomscheme://oauth/callback/instagram/.
The important part is the scheme: mycustomscheme. Choose something unique.

The Website URL is not important. Choose whatever you want.

When the registration is complete you will get a Client ID. It looks something like this: c46692e6a1e57748a2b3f2b553f84c67. Keep it for later. You can access and edit this information any time.

Tip: You can manage your applications to revoke access. This might come in handy later, during debugging, in case you’ve already signed in and granted your application access but want to deny it. (Suppose you want to test the error case.)

Create an Android Plugin

The plugin will handle the response URI, extract the access token and send it to the Unity app. It will be written in Java.

Time to open Eclipse. Create a new Android Application Project. Most of the parameters are not important, such as application name, project name or Android SDK versions. They are controlled by Unity’s build settings. You should, however, pay attention to 3 things:

  1. Package name. For example: com.companyname.appname. Make sure this is the same as the Bundle Identifier in Unity’s Player Settings.
  2. Check the “Mark this project as a library” to produce a JAR file. (This can also be done later from the project properties under Android.)
  3. Activity Name. I’ll use MainActivity (the default) in this example. You will need to use this later when adding the plugin to your Unity project.

Add classes.jar to the Classpath

The location of classes.jar depends on your installation of Unity. The default location is: C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androidplayer\bin\classes.jar. More info here.

  1. Open: Project => Properties => Java Build Path => Add External JARs…
  2. Locate and select classes.jar

MainActivity.java

We will subclass UnityPlayerActivity and implement onNewIntent:

package com.companyname.appname;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

public class MainActivity extends UnityPlayerActivity {
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleAccessToken(intent);
    }

    private void handleAccessToken(Intent intent) {
        Uri uri = intent.getData();
        if (uri != null && uri.toString().startsWith("mycustomscheme")) {
            String accessToken = "";
            if (uri.getFragment() != null) {
                accessToken = uri.getFragment().replace("access_token=", "");
                Log.d("Unity", "Found access token: " + accessToken);
            } else {
                Log.d("Unity", "Access token not found. URI: " + uri.toString());
            }
            UnityPlayer.UnitySendMessage("AccessManager", "OnAccessToken", accessToken);
        }
    }
}

Our new activity extends the standard UnityPlayerActivity class. The onNewIntent method will be called when the activity is re-launched. If a URI is found, it is parsed and the access token is extracted. UnityPlayer.UnitySendMessage sends the token to a script inside Unity, where AccessManager is the name of the game object and OnAccessToken is the name of the method. We’ll later create them in Unity.

That’s it. You should find an output JAR file in the project’s bin folder. We’ll copy it later to the Unity project.

Tip: When writing log output (Log.d) I used “Unity” for the tag. This is the same tag that Unity uses for Debug.Log. So when I use logcat to read the log output I can filter easily:

adb logcat Unity:V *:S

Error Handling

The response URI for a successful sign-in looks like this:

mycustomscheme://oauth/callback/instagram/#access_token=316140977.f598def.cf36764c5c6f4488fa95362592f14f91

For a failure it looks something like this:

mycustomscheme://oauth/callback/instagram/?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request.

When the code above detects there is no fragment, it passes an empty string instead of an access token.

The Unity App

The Unity app needs to open the sign-in URL when the user chooses to sign in. And it needs to receive the access token from the plugin we wrote earlier.

Open Sign-In URL in Browser

All you need to do is open this link:

https://instagram.com/oauth/authorize/?client_id=c46692e6a1e57748a2b3f2b553f84c67&redirect_uri=mycustomscheme%3A%2F%2Foauth%2Fcallback%2Finstagram%2F&response_type=token
  • Fill in the Client ID you received from Instagram.
  • Use the Redirect URI you configured earlier. Replace the colon with “%3A” and all the slashes with “%2F” as above.

Notice the last parameter: response_type=token. This instructs Instagram to use the implicit flow and return the access token in the response URI.

The following Boo code simply formats the link string and calls Application.OpenURL:

SIGN_IN_URL_FORMAT = "https://instagram.com/oauth/authorize/?client_id={0}&redirect_uri={1}&response_type=token"
CLIENT_ID = "c46692e6a1e57748a2b3f2b553f84c67"
SIGN_IN_REDIRECT_URL = "mycustomscheme%3A%2F%2Foauth%2Fcallback%2Finstagram%2F"

signInUrl = SIGN_IN_URL % (CLIENT_ID, SIGN_IN_REDIRECT_URL)
Application.OpenURL(signInUrl)

Handling the Response

Create a game object named AccessManager and attach a script to it with a method named OnAccessToken. Make sure to use the same game object and method names as in the Java plugin code (UnityPlayer.UnitySendMessage). The method should have one parameter of type string, which will receive the access token. You can then use it to access Instagram API. (You may also want to store it using PlayerPrefs.)

Example code in Boo:

def OnAccessToken(accessToken as string):
    Debug.Log("Received access token: " + accessToken)
    PlayerPrefs.SetString("access_token", accessToken)
    // do something useful here

Adding the Plugin

The last part is to add the plugin we created earlier to the Unity project:

  1. Create a subdirectory Assets/Plugins/Android in your Unity project directory
  2. Copy the JAR file we created earlier to Assets/Plugins/Android.
  3. Create an AndroidManifest.xml file in Assets/Plugins/Android.
AndroidManifest.xml

Here we define our activity, MainActivity, as the main activity and also add an intent filter:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application android:icon="@drawable/app_icon" android:label="@string/app_name">
    <activity android:name=".MainActivity" android:label="@string/app_name" android:launchMode="singleTask" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:screenOrientation="sensor">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <intent-filter>
        <data android:scheme="mycustomscheme" />
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
      </intent-filter>
    </activity>
  </application>
</manifest>

Use the same names you used in the Java plugin:

  • Activity (.MainActivity). Mind the prepending dot!
  • Custom scheme (mycustomscheme)

There is no need to specify the package name (com.companyname.appname) here. It is determined by Unity’s Player Settings (“Bundle Identifier”).

Notice the parameter android:launchMode=”singleTask”. This is the only mode that worked for me.

During a build Unity will extend this file and merge a few bits into it, such as the bundle identifier (package name), Android versions and permissions. The extended file, with the same name, can be found in the Temp/StagingArea subdirectory, which is deleted when Unity is closed.

That’s it! Should be working now.

Testing the Custom Scheme URI

If you open a browser on your mobile and try to open your URI (e.g. mycustomscheme://oauth/callback/instagram/) you will most likely find yourself in a Google search results page. That’s just how the browser works. To manually open your URI you’ll have to find a creative way to open a URI directly. For example:

  • Create a simple HTML that redirects to your URI, copy (email?) it to the mobile device and open it with some HTML viewer
  • Create a redirecting HTML and put it on your server, if you have one
  • Open the URI using your Unity app using Application.OpenURL (haven’t tried this one myself)

What’s Next?

There are two issue worth mentioning. I haven’t finished investigating them and tinkering with them yet, but I’ll say a few words.

UnityPlayerNativeActivity and UnityPlayerProxyActivity

In this solution we subclassed UnityPlayerActivity. We ignored two other (important?) classes, UnityPlayerNativeActivity and UnityPlayerProxyActivity, which were introduced in Gingerbread.

  • UnityPlayerNativeActivity – Parallel to UnityPlayerActivity. Can also be subclassed.
  • UnityPlayerProxyActivity – The top activity; it determines which of the other two activities (regular or native) should be started.

It is possible that ignoring the native player results in reduced performance in some cases.

WebView

I managed to open a WebView inside Unity using gree’s plugin. I will soon try to use a WebView for signing in instead of sending the user to the browser.

Edit: I’ve tried using WebView and it didn’t work. It would not delegate handling of a custom scheme. Instead it shows an error. I assume this behavior could be overridden if I had a way to set my own WebViewClient and override shouldOverrideUrlLoading.

iOS

See you then.

Enjoy!

Tagged , , , , , . Bookmark the permalink.

43 Responses to Serverless authentication with Instagram

  1. ByDesign Games says:

    Thanks for the useful article. How’s iOS coming along? We’re in the same boat, needing to auth Instagram from iOS.

  2. Ken says:

    Good article – I’ll be using this in a project I am working on and will credit our efforts in the app. Any further progress on IOS? Thanks much.

    • Ofer Reichman says:

      Thanks, mate! Regarding the iOS, I outsourced that part to a friend. I’ll review his work and ask for permission to use it here.

  3. info says:

    Thank you for the good writeup. It actually used to be a enjoyment account it. Look complex to far brought agreeable from you! By the way, how could we keep up a correspondence?

    • Ofer Reichman says:

      glad you liked it! if you want to write me you can can find an email button on the top right. and a link in the About page.

  4. Andy says:

    This was a total lifesaver. Thanks a lot for the Android Plugin info.

    One question; The plugin works fine if the app is already running but seems to miss the access token if the app is launched for the first time. Have you any idea how to cope with this situation?

    I am guessing the plugin is started before the access manager is created so never receives the message.

    • Ofer Reichman says:

      glad to hear! xD
      what do you mean, the app is not already running? the order of things should be: 1) the app is started, 2) the user is sent to the browser to sign in, and finally 3) the control is returned to the app, together with the access token.
      how can the app not be running at stage #3?

      anyway, the access token message should not be missed. perhaps your access manager object does not exist in the first loaded scene?

  5. Andy says:

    Thanks for the response and its hard to explain these things and also I am not using your example verbatim.

    I am using your java code to send the data received from a custom URL scheme through to Unity. This works EVERY time if the app has already been launched but if its the first time the app has been launched it appears to not receive the access token. The receiving object is the only object in the scene apart from the camera.

    I have done a little work around and added some code to the java plugin that saves the access token to a file and I check the files content if I have not received an access token. This works fine so I will probably leave it there.

    Again, thanks a lot for the initial work. I would not have got as far as I have without it.

    • Ofer Reichman says:

      no problem. and feel free to continue this by mail (see button on top right.)

    • Sam says:

      I would like to hear how you went about doing this, I’m trying to do something similar. If you happen to read this I would be grateful for some tips :)

      • Ofer Reichman says:

        By the way, you should check your AndroidManifest.xml. Note the parameter android:launchMode=”singleTask”. It may affect how the application handles custom URIs.
        Maybe try messing with different values.

        • Sam says:

          Thanks for the response but I meant to replay to Andy. I have the launch mode set to singleTask already which fixed this issue of it even working in the first place.

          Thanks so much for your tutorial though I would really like to hear Andy’s work around though

  6. gulshan ara says:

    how can we check it? either it is working or not? i mean what will be the url?
    i have a bit of different scenario to implement. on user registration in my app an email is sent with a link when user click on that link it should open my application. in the link there is obviously some sort of access token or unique id but how i will create that url or custom scheme?

    • Ofer Reichman says:

      I’ll try to explain: mycustomscheme://oauth/callback/instagram/
      It has two parts:
      1. scheme (“mycustomscheme”)
      2. path (“oauth/callback/instagram”)

      You can choose any scheme you want. anything that you like. it can be “sugarplumfairy”. whatever. there is no configuration – just make sure that in every step in this tutorial you use “sugarplumfairy” when it says “mycustomscheme”. be consistent.

      the 2nd part, the path, can also be whatever you want. and, I as wrote in the post, this is less important, because whatever you choose, opening the uri will reach your app and your “onNewIntent” method. once you get there you can get the uri (intent.getData) and parse it. whatever uri you emailed your user, you will see it here. and you can check the path and/or the arguments and extract whatever unique key is in there.

      hope that clears it up a bit.

  7. gulshan ara says:

    Thanks Ofer Reichman, i need to know what url will open up my application or what combination i need to use? is there is any way i can check the url of my app? or lets say for example if i want to open pdf reader or any application what will be the url for that? as for instagram there is API integration, user account and many other things are involved so i got confused what will be the url to open up my application?

    • Ofer Reichman says:

      the URL for your application is defined in your AndroidManifest.xml file. read above and see the example (next to “android:scheme”).

      so, all you have to do is choose your URL and write it down in AndroidManifest.xml which this tutorial explains how to create.

  8. gulshan ara says:

    very helpful post. faced lots of challenges but finally resolved my all issues
    want to add something, if you want to check your application url from HTML Page or your onNewIntent instance is not called just put this functionality in onCreate method to communicate with unity.

    • Ofer Reichman says:

      glad I could help!
      regarding onCreate, I’ve been down that road before. I guess it depends on your application structure – mainly on the value of the launchMode parameter.
      hey, whatever works for you!

  9. Mort says:

    I am also wondering how I can edit this example to work also on the first time the app launches. Now app must be running when you use this example.

    Does someone have an easy work around for this?

  10. Nesta says:

    Looks like a terrific article! I’m going to give this a shot but I’m going to use the reddit api rather than instagram and see how it goes.

  11. Travis says:

    Hey, It works but I have one question for you:
    Do you know how I would make the OAuth callback make the original app open?

    What I have happening now is I start my app. It goes to the login part for OAuth (Fitbit specifically) and when it does the callbackUri, it ends up starting my app from 0 again so I don’t seem to be able to actually grab the callback code from it.

    Any idea how I would just make it switch back to the first instance instead of opening a new one?

    • Ofer Reichman says:

      I think it may have to do with the launchMode parameter.

      • Travis says:

        Well seems I got the crash to fix by putting the intent inside the
        “com.unity3d.player.UnityPlayerActivity”

        in the manifest but I still can’t get the access token back. I’ve tried making new objects and a small script that should be passed the token which would pass to where it needs to actually go but it isn’t getting called.

        I put some toasts and other stuff around to try and alert myself when I get called in the android jar but it’s not happening…

        http://hastebin.com/puheqowiqu.xml

        There’s all the code (most is typed from your example but I gotta be missing something)

  12. Travis says:

    Oh Sorry,
    Well the same as before, nothing.
    App Starts,
    Start Auth
    Return from OAuth to App
    App doesn’t get the code returned from Java. Doesn’t seem like any of the code from it is called.

    • Ofer Reichman says:

      I think your manifest isn’t pointing to the correct activity. It points to UnityPlayerActivity instead of your MainActivity. I guess that’s why it bypasses your code.

  13. Travis says:

    I don’t disagree, I just came across some posts online of people that had the crash problem and the fix they said was to put their intent inside the UnityPlayerActivity instead which yes, fixes the crash but then I think like you say as well, it’s not firing the code cause it’s not explicitly in “UnityPlayerActivity” itself…

    Guess I’ll keep tinkering with it…

    • Travis says:

      I just noticed that I forgot something in my Activity. Now looks like

      I forgot to have the Launcher part inside my activity.
      So now I’m back to the crashing part after it tries to return from the browser BUT I can see the inside of my app for a split second before it crashes…Not sure what exactly it means though 😛

  14. Travis says:

    Figured it out.
    I had to essentially delete EVERYTHING besides my plugin’s manifest details from the Android Manifest in the Plugins/Android folder.
    When I did that, Unity then re-merged everything and it works now.

    Thanks a ton. I never thought that would be the problem but I was getting desperate 😛

  15. Rich Hudson says:

    Not sure what is wrong with mine. I have copied the code as best I can and used Travis’ blog/video as well, but I simply cannot get :

    public void OnAccessToken(string accessToken)

    to be called in my code. It is as if the java jar file is not even there. I get no logging output from the jar. My redirect url does open the browser.

  16. Michael says:

    Hey there, I’m stuck at the “configure with custom scheme” step. The instagram site is refusing to let me use any scheme that isn’t http:// or https://, even after I register and try to update it. Is this a new requirement? Does this mean this entire process is borked?

  17. Mason says:

    I just wanted to thank you profusely for this post. I was searching and working for hours, and finally I did a fresh try with your solution and it works perfectly. Thank you so much.

Leave a Reply

Your email address will not be published. Required fields are marked *