Kotlin Android App using HAAPI

Kotlin Android App using HAAPI

Code Examples / mobile-integration

This tutorial shows how to run a code example that implements mobile OpenID Connect in a Kotlin App using the Hypermedia Authentication API (HAAPI). See also the API Driven Demo Client tutorial for an introductory guide that runs a simple web client.

Overview

The code sample provides a basic Android implementation to demonstrate the approach for handling hypermedia responses, and initially presents a view to start the authentication process:

Android Initial View

The authentication workflow is then managed as a series of fast API requests that return JSON responses. The mobile app processes each API response to perform various actions including rendering of forms:

Android Selection

The end user may be prompted with multiple screens, depending on the Identity Server configuration, and will then enter credentials:

Android Login Form

Finally the UI is updated to indicate that the user is authenticated, and the screen then receives an OAuth authorization code:

Android Authenticated View

From this point onwards the app will operate like a traditional mobile OAuth client, to redeem the code for tokens, then call APIs.

Get the Code

First ensure that you are running Android Studio 4.2 or later, then clone the GitHub repository and open its folder to view the Kotlin classes used:

Android HAAPI Project

Setup

Configure a Mobile OAuth Client

The Android HAAPI SDK article explains the details of how to configure an Android Hypermedia client. This includes configuration of Client Attestation settings for development, so that the app must prove its digital identity before it is allowed to attempt to authenticate end users.

The app’s other OAuth settings are specified in the Configuration class, and by default uses a mobile OAuth client called haapi-public-client. In this tutorial we are also using an internet SSL URL for the Identity Server:

class Configuration {
    companion object {
        const val host = "ef5540f40573.eu.ngrok.io"
        const val baseUrl = "https://$host"
        const val clientId = "haapi-public-client"
        const val redirectUri = "https://localhost:7777/client-callback"
        const val authorizationEndpoint = "oauth/v2/oauth-authorize"
        const val tokenEndpoint = "oauth/v2/oauth-token"
        const val scopes = "openid"
    }
}

Connectivity from Mobile Devices

When running the Curity Identity Server on your development computer, it can be useful to use ngrok to create a trusted SSL connection from an emulator or device. This can be done via the following simple script which assumes that the app connects to OAuth endpoints using the default port of 8443:

ngrok http 8443 -log=stdout &
sleep 5
BASE_URL=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[] | select(.proto == "https") | .public_url')
echo $BASE_URL

The generated URL such as https://ef5540f40573.eu.ngrok.io can then be configured as the Base URL in the Admin UI. Also set the protocol under Deployments to HTTP, as discussed in the Exposing Curity Using ngrok article.

Mobile ngrok

Run the App

Once the above configuration is complete you are ready to run the app and use whichever authenticators and users you have configured in the Curity Identity Server. In the following sections we will explain the key technical behavior implemented in the code example.

HAAPI Integration

Integrating the Hypermedia Authentication API is done in three layers summarized below. Curity will be providing libraries for the controller layer in the near future, so that integration requires very little code:

LayerImplemented ByDescription
DriverCurity LibraryProcessing of low level messages used for client attestation to prove the app’s identity
ControllerYour AppProcessing HAAPI responses, including form rendering, following redirects and polling the server
ViewYour AppOverriding the defaults and customizing presentation to take full control over the login user experience

Client Attestation Overview

When authentication is first triggered, the app sends a number of low level messages to prove it is the genuine app, based on the digital identity with which the app is signed and delivered to the Play Store.

This must be done before the app is allowed to attempt to authenticate the user. The main request to prove the client’s identity is a client assertion request with the following parameters:

Form ParameterExample Value
client_idhaapi-public-client
scopeurn:se:curity:scopes:haapi
grant_typeclient_credentials
client_assertion_typeurn:se:curity:attestation:client
client_assertioneyJraWQi…paTubjbwReZ0A

The server verifies these details and returns a JWT access token in a JSON response that must be sent with subsequent requests in the authentication workflow, along with a separate proof of possession token. See the HAAPI Whitepaper for further details on security.

Response FieldExample Value
access_tokeneyJraWQi…JUHpZbxbstkK41RJhA7vw
scopeurn:se:curity:scopes:haapi
token_typeDPoP

Authentication Requests

Once the client is attested it can start authenticating the user, which is done using standard OpenID Connect parameters, but with a custom accept header to indicate that the client wants to receive hypermedia responses:

 GET /oauth/v2/oauth-authorize?client_id=haapi-public-client&response_type=code&redirect_uri=haapi:start
 accept: application/vnd.auth+json

Various types of response such as the following may then be returned, in a similar manner to standard OpenID Connect responses. The data is received as JSON rather than HTML, so that it can be processed by non browser clients:

  • Forms to be rendered
  • Redirects to be followed
  • Instructions to open the system browser
  • Polling responses
  • Error Responses

A sample response when running the code example with an HTML Form authenticator is shown below. Note that the main response is a form to submit a username and password, but there are also links to related operations for user self sign up and account recovery that can also be rendered:

{
    "links": [
        {
            "href": "/authn/authentication/Username-Password/forgot-password",
            "rel": "forgot-password",
            "title": "Forgot your password?"
        },
        {
            "href": "/authn/authentication/Username-Password/forgot-account-id",
            "rel": "forgot-account-id",
            "title": "Forgot your username?"
        },
        {
            "href": "/authn/registration/Username-Password",
            "rel": "register-create",
            "title": "Create account"
        }
    ],
    "metadata": {
        "viewName": "authenticator/html-form/authenticate/get"
    },
    "type": "authentication-step",
    "actions": [
        {
            "template": "form",
            "kind": "login",
            "title": "Login",
            "model": {
                "href": "/authn/authentication/Username-Password",
                "method": "POST",
                "type": "application/x-www-form-urlencoded",
                "actionTitle": "Login",
                "fields": [
                    {
                        "name": "userName",
                        "type": "username",
                        "label": "Username"
                    },
                    {
                        "name": "password",
                        "type": "password",
                        "label": "Password"
                    }
                ]
            }
        }
    ]
}

Responses follow a generic API model and can therefore be processed in a generic way within the mobile app, using methods in the HaapiResponseProcessor class. The ViewsCreator class then does the finer grained work such as rendering the set of fields within a form model:

private fun processForm(form: JSONObject): List<View> {

    val title = form["title"].toString()
    val formViews = mutableListOf<View>()
    formViews.add(views.header(title))
    val model = form.getJSONObject("model")

    val fields = processFormFields(model)
    for (field in fields) {
        formViews.add(field.value)
    }

    val submitButton = views.button(model.optString("actionTitle", context.getText(R.string.submit_button).toString()))
    submitButton.setOnClickListener {

        val href = model["href"].toString()
        val url = if (href.startsWith("http")) href else "$baseUrl$href"
        val request = Request.Builder()
                .method(model.getString("method"), getBody(fields, model.getString("type")))
                .url(url)
                .build()

        apiCaller(request, true) { haapiResponse -> processHaapiResponse(haapiResponse) }
    }

    formViews.add(submitButton)
    return formViews
}

Using and Refreshing Access Tokens

After authentication the client will use access tokens to call APIs in the standard way, by sending them as bearer tokens in the HTTP Authorization header. With our example settings, access tokens will expire every 15 minutes, after which the app will need to refresh the access token.

This can be done very easily with a simple refresh token grant request, by posting form URL encoded parameters such as these to the token endpoint, and this can be easily coded with any Swift HTTP client:

Form ParameterExample Value
grant_typerefresh_token
client_idhaapi-public-client
refresh_token62a11202-4302-42e1-983e-b26362093b67

Logout

Since the mobile client uses the Hypermedia Authentication API and is cookieless, logout can be implemented in a very simple manner, by just removing tokens from the app and redirecting back to the home screen. Authentication can then be run again, and when testing it is easy to sign in as a different user.

Conclusion

The Hypermedia Authentication API provides a number of benefits to mobile apps, in terms of both finanical grade security and login usability:

  • The app gets an improved login user experience
  • The Identity Server continues to dictate security
  • The authentication workflow can be updated without needing to redeploy the app
  • Only genuine mobile clients can attempt user authentication
  • Man In the Browser (MITB) risks are eliminated for most authentication methods

Let’s Stay in Touch!

Get the latest on identity management, API Security and authentication straight to your inbox.

Keep up with our latest articles and how-tos using RSS feeds