Build an Android Application with Authentication

Build an Android Application with Authentication

With mobile apps becoming ever-present in users' lives, following best security practices has become essential in protecting your users and your apps. Implementing security alone and from the ground up can be costly for your development team, create a less-than-ideal user experience, and be susceptible to design/implementation errors. The easiest path to strong security, and a positive user experience, is to turn to the experts.

The Okta OIDC SDK can help with this in many ways: - Our security experts take the development effort of creating a secure authentication method off your plate - OAuth 2.0 and OpenID Connect allows your users to easily authenticate on your app with social login, using industry-standard secure protocols

This post will show you a simple example of how you can use Okta OIDC SDK to create a (Kotlin) Android app with authentication.

(TLDR) Download the Finished App with Android Authentication

If you want to follow along using a completed version of the app, clone this repository from GitHub:

git clone https://github.com/oktadeveloper/okta-oidc-android-example.git
cd okta-oidc-android-example

Create Your First Android App

First, you’ll need to download and install Android Studio. Next, launch the app and navigate to FileNew…​New Project…​. Then, create an "Empty Activity" for "Phone and Tablet". You should now see a screen similar to this:

Create new Android Project

Choose a "Package name" related to a domain you own (I own akaita.com, therefore I used com.akaita.myapplication in the example), a Save location you can remember, "Kotlin", and a "Minimum API level" of 23. Finally, click Finish.

Get Your Okta OIDC Configuration Settings

Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register to sign up for a new account. If you already have an account, run okta login. Then, run okta apps create. Select the default app name, or change it as you see fit. Choose Native and press Enter.

Use com.okta.dev-133337:/callback for the Redirect URI and set the Logout Redirect URI to com.okta.dev-133337:/logout (where dev-133337.okta.com is your Okta domain name). Your domain name is reversed to provide a unique scheme to open your app on a device.

What does the Okta CLI do?

The Okta CLI will create an OIDC Native App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. You will see output like the following when it’s finished:

Okta application configuration:
Issuer:    https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6

NOTE: You can also use the Okta Admin Console to create your app. See Create a Native App for more information.

Add a Sign-in Button to Your Android App

Now that you’ve finished up in Okta’s Admin Panel, head back to Android Studio and add a button to your res/layout/activity_main.xml. Replace the contents of this file with the XML below.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical">

    <Button
        android:id="@+id/signIn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Sign in" />

    </LinearLayout>

</ScrollView>

This button will be used to authenticate using OAuth 2.0 + OpenID Connect, thanks to the Okta OIDC SDK.

Give Your Android App Permission to Use the Internet

In your app/src/main/AndroidManifest.xml add the following XML just before the opening <application> tag:

<uses-permission android:name="android.permission.INTERNET" />

Add Okta OIDC SDK to Your Android App

Modify your app/build.gradle to add the Kotlin Android Plugins to make it easy to bind XML views to your code.

You’ll also need to add a manifestPlaceholder for appAuthRedirectScheme in app/build.gradle. Make sure it is consistent with your Redirect URIs. For instance, my redirect URIs look like com.okta.dev-123456:/callback, therefore my appAuthRedirectScheme is com.okta.dev-123456

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions' (1)
}

android {
    compileSdkVersion 29
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.akaita.myapplication"  (2)
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        manifestPlaceholders = [
            "appAuthRedirectScheme": "com.okta.dev-123456" (3)
        ]
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions { (4)
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation 'com.okta.android:oidc-androidx:1.0.17' (5)

    // Dependency required for Biometric-Authentication (which we will detail how to implement later on in this same article)
    implementation 'androidx.biometric:biometric:1.0.1'
}
1 Add the Kotlin Android Extension plugin
2 Keep you applicationId here.
3 The redirect URI for the application you created in your Okta Developer Console.
4 Okta OIDC libraries require Java 1.8 compatibility.
5 Add the dependency required for the Okta OIDC library.

Configure Okta OIDC in Your Android App

In your MainActivity class, add a couple of class properties and a couple of new methods, calling them from onCreate():

package com.akaita.myapplication

import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.okta.oidc.*
import com.okta.oidc.clients.sessions.SessionClient
import com.okta.oidc.clients.web.WebAuthClient
import com.okta.oidc.storage.security.DefaultEncryptionManager
import com.okta.oidc.util.AuthorizationException
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    /**
     * Authorization client using chrome custom tab as a user agent.
     */
    private lateinit var webAuth: WebAuthClient (1)

    /**
     * The authorized client to interact with Okta's endpoints.
     */
    private lateinit var sessionClient: SessionClient (2)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupWebAuth()
        setupWebAuthCallback(webAuth)
    }

    private fun setupWebAuth() {
        val oidcConfig = OIDCConfig.Builder()
                .clientId("20-character-long Client ID")
                .redirectUri("com.okta.dev-123456:/callback")
                .endSessionRedirectUri("com.okta.dev-123456:/logout")
                .scopes("openid", "profile", "offline_access")
                .discoveryUri("https://dev-123456.okta.com")
                .create()

        webAuth = Okta.WebAuthBuilder()
                .withConfig(oidcConfig)
                .withContext(applicationContext)
                .withStorage(SharedPreferenceStorage(this))
                .setRequireHardwareBackedKeyStore(false) (3)
                .create()
        sessionClient = webAuth.sessionClient
    }

    private fun setupWebAuthCallback(webAuth: WebAuthClient) { (4)
        val callback: ResultCallback<AuthorizationStatus, AuthorizationException> =
                object : ResultCallback<AuthorizationStatus, AuthorizationException> {
                    override fun onSuccess(status: AuthorizationStatus) {
                        if (status == AuthorizationStatus.AUTHORIZED) {
                            Log.d("MainActivity", "AUTHORIZED")
                            Toast.makeText(this@MainActivity, "Authorized", Toast.LENGTH_SHORT).show()
                        } else if (status == AuthorizationStatus.SIGNED_OUT) {
                            Log.d("MainActivity", "SIGNED_OUT")
                            Toast.makeText(this@MainActivity, "Signed out", Toast.LENGTH_SHORT).show()
                        }
                    }

                    override fun onCancel() {
                        Log.d("MainActivity", "CANCELED")
                        Toast.makeText(this@MainActivity, "Cancelled", Toast.LENGTH_SHORT).show()
                    }

                    override fun onError(msg: String?, error: AuthorizationException?) {
                        Log.d("MainActivity", "${error?.error} onError", error)
                        Toast.makeText(this@MainActivity, error?.toJsonString(), Toast.LENGTH_SHORT).show()
                    }
                }
        webAuth.registerCallback(callback, this)
    }
}
1 private lateinit var webAuth: WebAuthClient is a reference to the web client you will invoke to log in
2 private lateinit var sessionClient: SessionClient is a reference to the session you can use to conduct multiple operations after logging in, such as getting the user’s profile, revoking the authentication token, refreshing the authentication token, etc…​
3 setRequireHardwareBackedKeyStore(true) forces the app to require a device with encryption capabilities. This is the default configuration for Okta OIDC and it’s considered the best practice. Since you might to run this code in a emulator, you can temporarily set it to false.
4 private fun setupWebAuthCallback() is the place where you can define the action to take when authentication succeeds, fails or is canceled…​
You can create a utility method to automatically detect if your application is running in an emulator.

Make sure to use the values you wrote down during the Get your Okta OIDC configuration settings step to create val oidcConfig in private fun setupWebAuth().

Log in Using Okta OIDC

Now that you have a minimal configuration of Okta OIDC, it’s time to add a listener for the signIn button you added previously.

Create a payload using AuthenticationPayload and call webAuth.signIn() in MainActivity.kt.

import kotlinx.android.synthetic.main.main_activity.*

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    setupWebAuth()
    setupWebAuthCallback(webAuth)

    signIn.setOnClickListener {
        val payload = AuthenticationPayload.Builder()
            .build()
        webAuth.signIn(this, payload)
    }
}

This will instruct Okta OIDC SDK to launch a web browser in which your users can authenticate using their Okta credentials.

Once they successfully authenticate, you will be able to use sessionClient to do things like check their authentication status:

if (sessionClient.isAuthenticated) {
    // Do something specific to authenticated users
} else {
    // Do something to non-authenticated users
}

You can even download their profile:

private fun downloadProfile() {
    sessionClient.getUserProfile(object : RequestCallback<UserInfo, AuthorizationException> {
        override fun onSuccess(result: UserInfo) {
            Log.d("Profile", result.toString())
        }

        override fun onError(error: String?, exception: AuthorizationException?) {
            Log.d("Profile", error, exception.cause)
        }
    })
}

That’s it! You now have an Android app with robust OIDC authentication!

When you launch your app and click on the SIGN IN button, you will be greeted with Okta’s authentication portal, which you can use to authenticate users into your app:

Android browser login prompt

(Optional) Add Social login

It should be easy for users to authenticate into your app. Okta OIDC SDK helps you accomplish this by allowing users to use their social accounts to validate their identity.

With Okta, you can add an external Identity Provider—such as Google, Facebook, LinkedIn or Microsoft. To achieve this, connect to the external identity providers and ask them to trust Okta for your application. This is done in three simple steps:

Lastly, use the identity providers in your app:

signIn.setOnClickListener {
    val payload = AuthenticationPayload.Builder()
        .setIdp("{IdP-id}") // From your "Okta" admin console
        .setIdpScope("clientScope1", "clientScope2", "clientScope3") // Optional, in case the IDP requires it
        .build()
    webAuth.signIn(this, payload)
}

Now, when a user clicks your SIGN IN button, they’ll be greeted with the login page of the IDP of your choice, which will be used to authenticate users into your app.

(Optional) Biometric Login

Additionally, biometrics can be used to access sessions created by Okta OIDC. The BiometricPrompt recently published by the Android team makes it a very feasible option, taking a lot of the complexity off your hands and offering a unified familiar & native experience for users on all variants of Android (Google devices, Samsung devices, …​).

BiometricPrompt uses the available resources in each device to offer whichever options are possible. Those include Iris authentication, fingerprint authentication, PIN authentication, Pattern authentication, etc.

In essence, there are two components to be taken into account:

  1. BiometricPrompt can be used to only allow biometrically authenticated users into your app, or into specific sections of your app

  2. You can (and should) instruct Okta OIDC SDK to store all data in a biometrically protected encryption system

I created an easy-to-launch Kotlin wrapper of BiometricPrompt for you:

import androidx.biometric.BiometricConstants.ERROR_NEGATIVE_BUTTON
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
import androidx.fragment.app.FragmentActivity
import java.util.concurrent.Executors

class Biometric(
    fragmentActivity: FragmentActivity,
    onSuccessListener: () -> Unit,
    onCancelListener: () -> Unit,
    onErrorListener: (Int, String) -> Unit) {

    private val mCallback: BiometricPrompt.AuthenticationCallback =
        object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                if (errorCode == ERROR_NEGATIVE_BUTTON) {
                    onCancelListener()
                } else {
                    onErrorListener(errorCode, errString.toString())
                }
                prompt.cancelAuthentication()
            }

            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                onSuccessListener()
            }
         }

    private val prompt: BiometricPrompt
    private val promptInfo: PromptInfo

    init {
        prompt = BiometricPrompt(fragmentActivity, Executors.newSingleThreadExecutor(), mCallback)
        promptInfo = PromptInfo.Builder()
            .setTitle("Biometric authentication succeeded")
            .setDeviceCredentialAllowed(true)
            .setConfirmationRequired(true)
            .build()
    }

    fun show() {
        prompt.authenticate(promptInfo)
    }
}

Simply trigger this so that when a user who is not biometrically authenticated tries to launch MainActivity, they are only allowed through if they biometrically authenticate in their device. Also, remember to use GuardedEncryptionManager to store Okta OIDC data:

import com.okta.oidc.storage.security.DefaultEncryptionManager
import com.okta.oidc.storage.security.EncryptionManager
import com.okta.oidc.storage.security.GuardedEncryptionManager

private var currentEncryptionManager: EncryptionManager? = null
private var keyguardEncryptionManager: GuardedEncryptionManager? = null

override fun onResume() {
    super.onResume()

    // Check whether the user has already authenticated using the device's authentication method. If it hasn't, ask them to do it
    if (currentEncryptionManager?.isUserAuthenticatedOnDevice?.not() == true) {
        showKeyguard()
    }
}

private fun showKeyguard() {
    Biometric(
        fragmentActivity = this,
        onSuccessListener = {
            Log.d("MainActivity", "Biometric authentication succeeded")
            Toast.makeText(this, "Biometric authentication succeeded", Toast.LENGTH_SHORT).show()
        },
        onCancelListener = {
            runOnUiThread {
                Log.d("MainActivity", "Biometric authentication cancelled")
                Toast.makeText(this, "Biometric authentication cancelled", Toast.LENGTH_SHORT).show()
                finish()
            }
        },
        onErrorListener = { code, message ->
            runOnUiThread {
                Log.d("MainActivity", "Biometric authentication failed")
                Toast.makeText(this, "Biometric authentication failed", Toast.LENGTH_SHORT).show()
                finish()
            }
        }).show()
}

private fun setupWebAuth() {
    keyguardEncryptionManager = GuardedEncryptionManager(this, Int.MAX_VALUE)

    webAuth = WebAuthBuilder()
        .withConfig(oidcConfig)
        .withContext(applicationContext)
        .withCallbackExecutor(null)
        .withEncryptionManager(keyguardEncryptionManager)
        .create()
    sessionClient = webAuth.sessionClient
}

Now, whenever a user opens a new instance of your app, they will be able to access it using their biometric information. In the below example, a Google Pixel phone, the authentication is a fingerprint:

Android fingerprint prompt

Learn More About Android Auth and OIDC

It’s now easier than ever to implement OAuth 2.0 and OpenID authorization thanks to Okta OIDC SDK. A high effort, high maintenance chore just became a very straightforward task.

By taking advantage of BiometricPrompt and Kotlin, we can make our apps even safer, while still creating a seamless user experience that feels native to each manufacturer’s UI style.

Although the example we’ve created here does enough to satisfy the needs of the majority of apps, Okta OIDC SDK doesn’t stop there. The Okta OIDC Android repository contains a variety of ideas and suggestions to improve user experience like:

  • Using your own OkHttp client

  • Using a custom UI to log in

  • Having fine-grained control over session tokens' expiration, refresh, etc…​

  • Settings to handle preference of browser client for the authentication process (Chrome, Samsung browser, Firefox, etc…​)

As a reminder, you can find the source code for this example on GitHub.

This post has given you the foundations to set up a successful OIDC client. If you want to deepen your knowledge around modern authentication systems, check these additional resources on Android, OAuth 2.0 and OpenID Connect:

If you enjoyed this blog post and want to see more like it, follow @oktadev on Twitter, subscribe to our YouTube channel, or follow us on LinkedIn. As always, please leave your questions and comments below—we love to hear from you!

Changelog:

Okta Developer Blog Comment Policy

We welcome relevant and respectful comments. Off-topic comments may be removed.