Sample code for mobile apps

1. Introduction

Relying parties may also want to develop an mobile App (on Android or iOS) to use the resources on their site, such as accessing the user’s private info like photo, order detail etc. Such mobile Apps usually use protocols like OAuth to get the user’s authentication via tokens and use the tokens to retrieve the user’s private data. This document describes the detailes of implementing a particular technique for the iphone and Android.

2. Basic workflow

Workflow of a typical user resource retrieval includes:

  • Login

User login into the RP site (with federated login or legacy login). The RP site ask user’s permission for accessing some of the user’s private data, user grant the permission and finish the login process.

  • Get token

The RP site sends the token back to the mobile App, and the mobile App detects that the RP site has finished the login/authentication process and the token is available. The mobile App fetch the token.

  • Retrieve data

The mobile App uses the token to call the RP’s web service API to retrieve the data.

3. Sample code and key technologies

3.1. iOS

3.1.1. Browse the login page with UIWebView

The iOS provides both the default Safari browser and the UIWebView component for mobile Apps to launch a browser and the view the specified URL. But the default Safari browser is a stand-alone application which is very hard to communicate with the App to deliver the token. The UIWebView component provides similar features like the Safari browser, and it is compiled as part of the mobile App, so it is convenient to access the UIWebView’s page title or cookie etc. In our practice, we choose UIWebView to do the login/authenticate process.

(1) Create the UIWebView page in InterfaceBuilder

Create a new UIViewController and drag the UIWebView component onto it in InterfaceBuilder. Add some necessary buttons like “back” and “forward” (this is optional).

(2) Display the UIWebView page using UINavigationController

We recommend to use the UINavigationController to display the login page and navigate back the main page after getting the token.

1. Create a UINavigationController (directly in the main window delegate in our example).

UINavigationController *theNavController = [UINavigationController alloc];

[theNavController initWithRootViewController:self.startView];

self.navigationController = theNavController;

[theNavController release];

self.window.rootViewController = self.navigationController;

Note that we have set our main page as the first view (startView) in the UINavigationController so that it will be displayed when the program starts. Our start view looks like below, but this view is entirely up to the RP.

(3) Set the request URL display the web page

In the login view, we should call the “loadRequest” method of UIWebView to load the login web page that we want to use. Here is a sample code to do this work:

- (void)loadAuthenticateUrl:(NSString *)authenticateUrl delegate:(id) aDelegate {

self.delegate = aDelegate;

self.webView.delegate = self;

self.webView.scalesPageToFit = YES;

self.domain = [[NSURL URLWithString:authenticateUrl] host];

[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:authenticateUrl]]];

}

Note that in the above sample code, we did several key steps.

  1. We setup the delegate for which we will call back to user code when we have to token

  2. Set the UIWebView’s delegate to this class so that it will call back the webViewDidFinishLoad when the UIWebView finish loading any page.

  3. We extract the domain string from the login URL because we will use this domain to extract the token from the cookies.

3.1.2. Get the token via cookie

After the RP site finishes the login/authentication process, it will redirect the user’s browser to a success page. In that success page, a cookies is set with key = ‘oauth_token’ and the value is the the access token. This token should be delivered back the the mobile App’s main program so that it can use this token to get the user’s private data. The code that extract the token from the cookies is shown below:

- (NSString*)getTokenFromCookie {

NSHTTPCookie *cookie;

NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];

for (cookie in [cookieJar cookies]) {

if ([[cookie domain] isEqualToString:self.domain]) {

if ([[cookie name] isEqualToString:@"oauth_token"]) {

return [cookie value];

}

}

}

return nil;

}

- (void)webViewDidFinishLoad:(UIWebView *)theWebView

{

NSString* token = [self getTokenFromCookie];

if (token != nil) {

[self.delegate gotToken:token];

[self.navigationController popViewControllerAnimated:YES];

}

}

Note that the “webViewDidFinishLoad” is a delegate function that UIWebView calls when it finish loading any page. The page may not be the confirmation page which carries the user’s authorized token, so we have to check if the token is already in the cookies and if so we will close the login view, call the user delegate to pass the token and go back to main page.

3.1.3. Retrieve data

After getting the token, we could use NSURLConnection to retrieve the data.

// Put the following line in the place where the user data retrieval begins.

NSURLConnection *aConnection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]] delegate:self];

// Callback for receiving data

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

NSLog(@"Did receive data, size = %d", [data length]);

self.receivedData = data;

}

3.2. Android

3.2.1. Setup your main activity to start OAuth authentication

You can create a menu item called ‘login’ or ‘sign in’ to allow users to start the authentication.

(1) Create a menu resource

The easiest way to create a menu is defining a menu and all its items in an XML menu resource file, then inflating the menu resource in your application code. Here’s the example menu resource named ‘my_menu.xml’:

<?xml version="1.0" encoding="utf-8"?>

<menu

xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/login" android:title="@string/login" />

<item android:id="@+id/exit" android:title="@string/exit" />

</menu>

(2) Inflate the menu resource and respond to user action

You can use MenuInflater.inflate() to inflate a menu resource in Activity.onCreateOptionsMenu() callback method. In Activity.onOptionsItemSelected() callback method, you can respond to the user action. The following sample code shows that if the user click the ‘login’ menu item, a WebViewActivity, which we’ll elaborate later, is started to handle the authentication and return the result to the calling activity.

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflating the menu resource.

MenuInflater inflater = getMenuInflater();

inflater.inflate(R.menu.my_menu, menu);

return true;

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

// Start the WebViewActivity to handle the authentication.

case R.id.login:

Intent intent = new Intent(this, WebViewActivity.class);

intent.setData(Uri.parse(YOUR_AUTHENTICATION_ENDPOINT));

startActivityForResult(intent, 0);

return true;

// Exit.

case R.id.exit:

finish();

return true;

}

return super.onOptionsItemSelected(item);

}

3.2.2. Use WebView widget to do the authentication

The Android os provides a default web browser. Since the web browser is a stand-alone application and only provides its bookmark and history information to other applications, it is difficult to get the OAuth token from it. The Android os also provides a widget called WebView, which allows you to display web pages in it and to access the page’s information such as page title, cookies etc. In our practice, we choose WebView to do the work.

(1) Create the WebView activity

The activity is simple, which only contains a WebView widget.

public class WebViewActivity extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// Allow the title bar to show loading progress.

requestWindowFeature(Window.FEATURE_PROGRESS);

WebView webview = new WebView(this);

setContentView(webview);

// Load the page

Intent intent = getIntent();

if (intent.getData() != null) {

webview.loadUrl(intent.getDataString());

}

}

(2) Make WebViewActivity to behave like a web browser

The default behavior of a WebView is not what you expect. When you click a link in the WebView, it starts the default web browser to view the new page instead of displaying it in the WebView itself. There are several customization points where you should add your own behavior.

First, most web pages nowadays require enabling JavaScript. You can enable it by modifying the WebSettings of the WebView, here’s the example:

// Enable JavaScript.

webview.getSettings().setJavaScriptEnabled(true);

Second, it’s always a good practice to show the user which page is being viewed and whether the page is loading or loaded. Create and set your own WebChromeClient and WebViewClient to add these functions.

webview.setWebChromeClient(new WebChromeClient() {

// Show loading progress in activity's title bar.

@Override

public void onProgressChanged(WebView view, int progress) {

setProgress(progress * 100);

}

});

webview.setWebViewClient(new WebViewClient() {

// When start to load page, show url in activity's title bar

@Override

public void onPageStarted(WebView view, String url,

Bitmap favicon) {

setTitle(url);

}

});

(3) Get the token via cookie/title

After the site finishes the authentication, it will redirect the page to a success page, in which the token is contained. The site may use the cookie or page title to deliver the token. For instance, when the success page is loaded, a cookie is set like ‘oauth_token=YOUR_OAUTH_TOKEN’. You can check the cookie each time a page is finished. If the ‘oauth_token’ cookie is found, you can extract the token from the cookie and return it to the calling activity. The following sample code shows how to detect and extract the token from the cookie.

webview.setWebViewClient(new WebViewClient() {

@Override

public void onPageFinished(WebView view, String url) {

CookieSyncManager.getInstance().sync();

// Get the cookie from cookie jar.

String cookie = CookieManager.getInstance().getCookie(url);

if (cookie == null) {

return;

}

// Cookie is a string like NAME=VALUE [; NAME=VALUE]

String[] pairs = cookie.split(";");

for (int i = 0; i < pairs.length; ++i) {

String[] parts = pairs[i].split("=", 2);

// If token is found, return it to the calling activity.

if (parts.length == 2 &&

parts[0].equalsIgnoreCase("oauth_token")) {

Intent result = new Intent();

result.putExtra("token", parts[1]);

setResult(RESULT_OK, result);

finish();

}

}

}

});

If the site delivers the token by page title, you can get the title string by WebView.getTitle(), and extract the token from the title string.

3.2.3. Use the token to access data

After the WebViewActivity destroys itself, the workflow is return to the calling activity. Since the WebViewActivity is called by startActivityForResult, the calling activity’s onActivityResult() callback will be called. Here’s the example:

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

switch (requestCode) {

case 0:

if (resultCode != RESULT_OK || data == null) {

return;

}

// Get the token.

String token = data.getStringExtra("token");

if (token != null) {

/* Use the token to access data */

}

return;

}

super.onActivityResult(requestCode, resultCode, data);

}

3.2.4. Remember user’s login state

A lot of sites asks the user whether to remember the login state. If the user chooses to remember, the next time the user returns to the site, the site will not require the user to login. This is supported by most web browsers. If you want the WebViewActivity has the same function, you can use CookieSyncManager and CookieManager to do the job.

First, when you create your main activity, create a CookieSyncManager with the application context also.

CookieSyncManager.createInstance(getApplicationContext());

Then, in your WebViewActivity, you can start the sync process when WebViewActivity is resumed and stop the process when it is paused. The CookieSyncManager will start up another thread to sync the cookie between RAM and permanent storage periodically. You can also force the sync manually instead of waiting for the timer to trigger, for instance, in WebViewClient.onPageFinshed().

@Override

protected void onPause() {

super.onPause();

CookieSyncManager.getInstance().stopSync();

}

@Override

protected void onResume() {

super.onResume();

CookieSyncManager.getInstance().startSync();

}