Single Sign-On With AppAuth Android [Step By Step]

Appauth android is a client SDK for native apps to authenticate and authorize end-users using OAuth 2.0 and OpenID Connect. Available for iOSmacOSAndroid, and Native JS environments. it implements modern security and usability best practices for native app authentication and authorization.

It strives to directly map the requests and responses of those specifications while following the idiomatic style of the implementation language. In addition to mapping the raw protocol flows, convenience methods are available to assist with common tasks like performing an action with fresh tokens.

Checkout my other post using google API’s:

Google Places Autocomplete Android Example

How To Get Current Latitude And Longitude In Android

How Appauth Android Works

AppAuth supports Android API 16 (Jellybean) and above. Browsers which provide a custom tabs implementation are preferred by the library, but not required. Both Custom URI Schemes (all supported versions of Android) and App Links (Android M / API 23+) can be used with the library.

Appauth Workflow

Lets create the example on google appauth using oauth in android.

1. Add AppAuth As A Build Dependency

Add the following line to the build.gradle to the in the app directory. This will make the AppAuth library available to your project.

implementation 'net.openid:appauth:0.7.0'

2. Create The ServiceConfiguration

First, AppAuth must be instructed on how to interact with the authorization service. This can be done either by directly creating an AuthorizationServiceConfiguration instance.

Directly specifying an AuthorizationServiceConfiguration involves providing the URIs of the authorization endpoint and token endpoint.

val serviceConfig = AuthorizationServiceConfiguration(
    Uri.parse("https://accounts.google.com/o/oauth2/v2/auth"), // authorization endpoint
    Uri.parse("https://www.googleapis.com/oauth2/v4/token") // token endpoint
)

3.Build The AuthorizationRequest

Once you have an instance of AuthorizationServiceConfiguration, you can now build an instance of AuthorizationRequest which describes actual authorization request, including your OAuth client id, and the scopes you are requesting.

val clientId = "511828570984-fuprh0cm7665emlne3rnf9pk34kkn86s.apps.googleusercontent.com"
val redirectUri = Uri.parse("com.google.codelabs.appauth:/oauth2callback")
val builder = AuthorizationRequest.Builder(
    serviceConfig,
    clientId,
    ResponseTypeValues.CODE,
    redirectUri
)
builder.setScopes("profile")

val authRequest = builder.build()

Be sure to register your own client ID when developing your own apps, and update the clientId, and redirect URI values with your own (and the custom scheme registered in the AndroidManifest.xml). If using a different OAuth server, then you’ll need to register a client for that server, following their documentation.

4. Perform The Authorization Request

startActivityForResult call using an Intent returned from the AuthorizationService.

val authService = AuthorizationService(this)
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
startActivityForResult(authIntent, RC_AUTH)

5. Handle The Authorization Response

Once the authorization flow is completed in the browser, the authorization service will redirect to a URI specified as part of the authorization request, providing the response via query parameters. In order for your app to capture this response, it must register with the Android OS as a handler for this redirect URI.

When a custom scheme is used, AppAuth can be easily configured to capture all redirects using this custom scheme through a manifest placeholder:

android.defaultConfig.manifestPlaceholders = [
  'appAuthRedirectScheme': 'com.example.app'
]

Alternatively, the redirect URI can be directly configured by adding an intent-filter for AppAuth’s RedirectUriReceiverActivity to your AndroidManifest.xml:

<activity
        android:name="net.openid.appauth.RedirectUriReceiverActivity"
        tools:node="replace">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.google.codelabs.appauth"/>
    </intent-filter>
</activity>

If an HTTPS redirect URI is required instead of a custom scheme, the same approach (modifying your AndroidManifest.xml) is used:

<activity
        android:name="net.openid.appauth.RedirectUriReceiverActivity"
        tools:node="replace">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="https"
              android:host="app.example.com" android:path="/oauth2redirect"/>
    </intent-filter>
</activity>

6. Handling The Authorization Response

Upon completion of the authorization flow, the completion Intent provided to performAuthorizationRequest will be triggered. The authorization response is provided to this activity via Intent extra data, which can be extracted using the fromIntent() methods on AuthorizationResponse and AuthorizationException respectively:

@Override

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

  if (requestCode == RC_AUTH) {

    AuthorizationResponse resp = AuthorizationResponse.fromIntent(data);

    AuthorizationException ex = AuthorizationException.fromIntent(data);

    // … process the response or exception …

  } else {

    // …

  }

}

7. Exchanging The Authorization Code

Given a successful authorization response carrying an authorization code, a token request can be made to exchange the code for a refresh token:

mAuthService?.performTokenRequest(
    resp.createTokenExchangeRequest()) { resp, ex ->
    if (resp != null) {
        mStateManager?.updateAfterTokenResponse(resp, ex)

        Log.d("accessToken",resp.accessToken)

        // exchange succeeded
    } else {
        // authorization failed, check ex for more details
    }
}

That’s it. We got an access token to access our API.

Access Token

8. Making API call using Access Token

While you can get the tokens directly from the token response, those tokens expire and must be refreshed occasionally. Using AuthState and making your REST API calls inside authState.performActionWithFreshTokens is recommended, as it will automatically ensure that the tokens are fresh (refreshing them when needed) before executing your code.

In our case, I am getting the username and profile picture of the logged in user.

if (mStateManager?.current?.isAuthorized!!) {
            Log.d("Auth", "Done")
            button_login.text = "Logout"
            mStateManager?.current?.performActionWithFreshTokens(
                mAuthService!!
            ) { accessToken, idToken, exception ->
                ProfileTask().execute(accessToken)
            }

        }
    }

    inner class ProfileTask : AsyncTask<String?, Void, JSONObject>() {
        override fun doInBackground(vararg tokens: String?): JSONObject? {
            val client = OkHttpClient()
            val request = Request.Builder()
                .url("https://www.googleapis.com/oauth2/v3/userinfo")
                .addHeader("Authorization", String.format("Bearer %s", tokens[0]))
                .build()
            try {
                val response = client.newCall(request).execute()
                val jsonBody: String = response.body().string()
                Log.i("LOG_TAG", String.format("User Info Response %s", jsonBody))
                return JSONObject(jsonBody)
            } catch (exception: Exception) {
                Log.w("LOG_TAG", exception)
            }
            return null
        }
        override fun onPostExecute(userInfo: JSONObject?) {
            if (userInfo != null) {
                val fullName = userInfo.optString("name", null)
                val imageUrl =
                    userInfo.optString("picture", null)
                if (!TextUtils.isEmpty(imageUrl)) {
                    Glide.with(this@LoginActivity).load(imageUrl).into(imgProfile);
                }
                if (!TextUtils.isEmpty(fullName)) {
                    textUsername.setText(fullName)
                }
                val message = if (userInfo.has("error")) { java.lang.String.format(
                        "%s [%s]", getString(R.string.request_failed), userInfo.optString("error_description", "No description"))
                } else {
                    getString(R.string.request_complete)
                }
                Snackbar.make(imgProfile, message, Snackbar.LENGTH_SHORT).show()
            }
        }
    }

That’s it. we have implemented the Appauth using oauth in android.

Download this example in Github.

Conclusion

Thanks for reading.

Please share if you like this post. Also, provide your feedback in the comments.

Leave a Reply

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