Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android SSO Okta Integration Example

Tags:

android

okta

Don't know a lot about Okta and Android. Does anyone know of a good tutorial which shows how to wire an android application into the Okta framework. Or do I implement a SAML SSO implementation and then Okta is associated to that? Any code examples appreciated - especially one showing Android implementation of generic SSO, if such a thing exists.

like image 428
Stephen McCormick Avatar asked Jan 18 '16 19:01

Stephen McCormick


1 Answers

OK, alot of ground to cover here and some of the work I did not do. But the basic idea is that on the server side (we use .Net) we created a SAML communication layer using "kentor". I did not work with this, but idea is software communicating the the client's Identity Provider (IDP) for SSO (Okta for example). The IDP client usually has to provide XML meta data which has security info and ultimately a URL, and you provide them with your SSO xml meta data (sorry, I did not work on that part!).

Basically from there it is pretty straight forward on the Android side. The bottom line is that the above interaction results in a url that the SSO client provides that you will use on the Android side to create a webview, which will allow them to enter their login information for verification.

We have the URL hardcoded since we create a white label product specifically for the customer (you will see that as Constants.SINGLE_SIGNON_URL below) but there is nothing that stops you from passing the URL back after a customer passes a Organization Code for SSO in (we are working on that now). In other words you store the URL or generate the URL based on which customer and then return that when the device passes you an Organizatioal Code. The URL is actually to your server, which redirects to the IDP (Okta) login page for SSO. This is because the response from OKTA needs to go to your server where ultimately it will be sent back by redirect to your webview. We used the cookies to then store the resulting username to allow the normal login process. Probably a lot of different ways to do that, and Okta even provides a native mobile device capability, but the customer has to support that.

Here is a diagram that hopefully will spell out some high level pieces of this:

enter image description here

The code only covers 1), 2) and 5) in the above diagram. 1) Is pretty obvious the call to the WebView. 2) is really the call to the Constants.SINGLE_SIGNON_URLwhich hits your server, which should redirect to the IDP page. When the user logs in there, it get sent back to your Service (SP) and is redirected back to your WebView. Again, we stored something in the cookie to pull out to continue our normal login.

One key is to realize that the WebView's shouldOverrideUrlLoading() is called several times. Ignore all of those except the one that sends back your server's URL, at which point you pull out the data you need (in our case the login info that the server had verified). This is seen in the call GlobalState.getInstance().currentUserName = getCookieValue("_username" ,cookies);

Probably not explaining this very well (and it has been a month or so!). Here is a sample of the SSOActivity where most of the work is done:

public class SSOActivity extends Activity {
    WebView webView;
    private Button mCancel;
    private Button mReset;

    /**
     * Grabs the specified variables out of the list of cookies
     * 
     * @param fieldName
     * @param cookies
     * @return
     */
    public String getCookieValue(String fieldName, final String cookies){     
        String CookieValue = null;

        String[] cookiessplit = cookies.split(";");
        for (String str : cookiessplit ) {
            if(str.contains(fieldName)) {
                String[] value=str.split("=");
                CookieValue = value[1];
                break;
            }
        }              
        return CookieValue; 
    }

    public void clearCookies() {
        try {
            android.webkit.CookieManager cookieManager = CookieManager.getInstance();
            cookieManager.removeAllCookie();
        }
        catch (Exception ex) 
        {
            Utilities.logException(ex);
            Utilities.logError("SSOActivity", "clearCookies() : " + ex.getMessage() );
        }
    }

    /**
     * Cancels the SSO request in Webview
     * 
     * @param view
     */
    public void cancelSSOClick (View view) {
        Utilities.logInfo("cancelSSOClick", "Cancel SSO click");
        setResult(Activity.RESULT_CANCELED, null);
        SSOActivity.this.finish();
    }

    /**
     * Resets and deletes cookies and SSOUrl if one exists
     * 
     * @param view
     */
    public void resetSSOClick (View view) {
        Utilities.logInfo("resetSSOClick", "Cancel SSO click");
        setResult(Activity.RESULT_CANCELED, null);
        clearCookies();
        SSOActivity.this.finish();
    }



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setResult(Activity.RESULT_OK, null);

        // Setup the web view. It will redirect to SSO site for login
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_sso);

        mCancel = (Button)findViewById(R.id.cancelSSO);
        mCancel.setTextColor(Color.WHITE);

        mReset = (Button)findViewById(R.id.resetSSO);
        mReset.setTextColor(Color.WHITE);

        webView = (WebView) findViewById(R.id.ssoViewer);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setSupportZoom(false);
        webView.setWebViewClient(new WebViewClient() {
           @Override
            public boolean shouldOverrideUrlLoading (WebView view, String url) {
                try {
                    // If coming from our system, then we need to check the cookie for username password, for
                    // some SSO this might be different than the base url. Check for both
                    if (url.equals(Constants.getBaseUrl()) || url.equals(Constants.SSO_RETURN_URL)) {

                        CookieManager cookieManager = CookieManager.getInstance();
                        final String cookies = cookieManager.getCookie(url);
                        GlobalState.getInstance().currentUserName = getCookieValue("_username" ,cookies);
                        SSOActivity.this.finish();
                        return true;
                    }
                } 
                catch (Exception ex) {
                    GlobalState.getInstance().currentUserName = "";
                    GlobalState.getInstance().currentPassword = "";
                    setResult(Activity.RESULT_CANCELED, null);
                    SSOActivity.this.finish();
                }

                return false;
           }

        });

        try {
            webView.loadUrl(Constants.SINGLE_SIGNON_URL);
        }
        catch (Exception ex) {
            Utilities.logException(ex);
            Utilities.logError("SSOActivity", "onCreate(), webView.loadUrl(ssoUrl) : " + ex.getMessage() );

        }

    }

}

Here is an example of the XML supporting the Activity:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/ssoViewerLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/button_layout"
                android:layout_width="match_parent"
                android:orientation="horizontal"
                android:layout_height="wrap_content"
                android:gravity="center|bottom"
                android:layout_alignParentBottom="true">      

                <Button
                    android:id="@+id/cancelSSO"
                    android:layout_marginTop="16dp"
                    android:layout_width="125dp"
                    android:layout_height="55dp"
                    android:layout_margin="5dp"
                    android:onClick="cancelSSOClick"
                    android:text="Cancel Login"
                    android:background="@drawable/button_login" />
                <Button
                    android:id="@+id/resetSSO"
                    android:layout_marginTop="16dp"
                    android:layout_width="125dp"
                    android:layout_height="55dp"
                    android:layout_margin="5dp"
                    android:onClick="resetSSOClick"
                    android:text="Reset SSO"
                    android:background="@drawable/button_login"/>
        </LinearLayout>
     <WebView
               android:id="@+id/ssoViewer"
               android:layout_width="fill_parent"
               android:layout_height="fill_parent"
               android:layout_above="@id/button_layout" />  
    </RelativeLayout>

Calling it else where in the code looks like this:

                        Intent viewIntent = new Intent(getActivity(), SSOActivity.class);
                        (getActivity()).startActivityForResult(viewIntent, Constants.SINGLE_SIGN_ON);

And finally what you should see:

enter image description here

Hope this helps!

like image 121
Stephen McCormick Avatar answered Oct 20 '22 11:10

Stephen McCormick