Using the Android SDK to Consume the Authentication API

Using the Android SDK to Consume the Authentication API

What Is the Hypermedia Authentication API?

The Hypermedia Authentication API is a feature of the Curity Identity Server. It enables clients to leverage OAuth and OpenID Connect and the Curity Identity Server's powerful authentication engine straight from client applications — without the need of a browser. If you want to learn more about the Hypermedia Authentication API, check out the overview article.

It's All About Security

The browser plays a vital role in OAuth and OIDC flows — it makes them more secure. Browser redirects ensure that the Authorization Server delivers data to the proper client, and not to an impersonator. Thus, removing the browser from OAuth and OIDC flows may at first seem a bit reckless. As we now use an API to fulfill a vulnerable authentication and authorization flow, we require another method to make sure that the client calling the API is indeed allowed to do so.

Two security features of the Curity Identity Server ensure that the Authentication API process is just as secure as browser flows:

  • client attestation
  • proof of possession tokens

Client Attestation

Client attestation is done by verifying the running application's package ID and the signing credentials. This is processed by the Android Operation System or iOS and bound to a cryptographic key pair. After a positive attestation, the client (the mobile app) is issued a Client Attestation Token (CAT) in the form of a JWT. The concrete implementation of the attestation is out of the scope of this article, but what is important to note, is that client attestation asserts a few things about the calling client application:

  1. The app was correctly signed with the credentials configured in the Curity Identity Server for the associated OAuth client. This means that the app has not been tampered with. For example, it hasn't been decompiled, had its code changed, and recompiled into a malicious client. When using these features, decompilation cannot provide any information that could help create an impersonated client.
  2. The app runs in a safe environment, that is, on a system that has not been rooted. Otherwise, it wouldn't be possible to securely perform the verification described above.

Demonstration of Proof-of-Possession

Proof-of-Possession tokens are another way to use access tokens. The most common way used in APIs is using Bearer tokens. Bearer tokens work a bit like cash — any client who possesses a Bearer access token can successfully call an API. Proof-of-Possession tokens behave more like credit cards — only legitimate owners of these tokens can use them to access the API. This further increases the security of the communication. Even if the access token is stolen, another party can't use it.

The Hypermedia Authentication API uses a standard for Demonstration of Proof-of-Possession. By adopting this standard, access tokens used to access the Authentication API are bound to a private key that only the legitimate client possesses.

The SDKs for Hypermedia Authentication API

Obtaining CATs and using DPoP to secure requests to the API is a security-sensitive process and quite complicated to implement. The method of obtaining a CAT requires a few different steps, and the DPoP standard requires the client to generate proper DPoP headers for every request. To facilitate the usage of these crucial security components, Curity has released SDKs to help you leverage the Hypermedia API: the driver SDK and model SDK. The driver SDK is responsible for handling the low-level security features of the API. The model SDK introduces classes representing possible responses from the authentication API. Normally you will work with the model SDK, which internally uses the driver SDK to handle communication.

Prerequisites for using the API

To call the API from an Android app, make sure of the following:

  1. Use at least version 6.2.2. of the Curity Identity Server.
  2. You will require a client with the API capability to use the Android attestation policy. These settings can be set using the admin UI (described below in each tab), but you can also use the following XML file to merge it with your server's configuration. Make sure to adjust values in the XML below to conform with your environment:

You can use this version of configuration to ease development and allow emulator usage. It has some relaxed requirements for validation. Remember that these options shouldn't be used in production unless necessary to support special devices.

  • In line 16, change the Token Service's name, if you don't use the default.
  • In line 23, set the proper client name.
  • In line 26, set the package name your app uses.
  • In line 27, enter the digest of the credentials used to sign your app. (Read below to learn how to get the digest.)
<config xmlns="http://tail-f.com/ns/config/1.0">
    <facilities xmlns="https://curity.se/ns/conf/base">
        <client-attestation>
            <android-policy xmlns="https://curity.se/ns/conf/client-attestation">
                <id>emulator-policy</id>
                <verify-boot-state>false</verify-boot-state>
                <minimum-security-level>software</minimum-security-level>
                <override-certificate-chain-validation>
                    <do-not-validate-certificate-chain/>
                </override-certificate-chain-validation>
            </android-policy>
        </client-attestation>
    </facilities>
    <profiles xmlns="https://curity.se/ns/conf/base">
        <profile>
            <id>token-service</id>            <type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
            <settings>
                <authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
                    <client-store>
                        <config-backed>
                            <client>
                                <id>client-name</id>                                <attestation>
                                    <android>
                                        <package-name>com.example.yourpackage</package-name>                                        <signature-digest>A3...4=</signature-digest>                                        <android-policy>emulator-policy</android-policy>
                                    </android>
                                </attestation>
                            </client>
                        </config-backed>
                    </client-store>
                </authorization-server>
            </settings>
        </profile>
    </profiles>
</config>

The configuration above can also be set using the admin UI.

  1. Create an attestation policy under the Facilities menu (scroll all the way to the bottom).
  2. Disable Verify Boot State, set Minimum Security Level to software and disable Override Certificate Chain ValidationAttestation Policy
  3. In the public client configured to be used with the API, enable the Hypermedia Authentication API capability. Enable HAAPI
  4. Then go to the Client Application Settings section and expand Advanced. Enable Attestation and configure according to the screenshot below. Here you can point to the attestation policy that was just created. Make sure to set the Package Name and Signature Digest accordingly. (Read below to learn how to get the digest.) Development Client Attestation

Use the following configuration for production environments. It uses default settings for the attestation policy.

  • In line 4, change the Token Service's name, if you don't use the default.
  • In line 11, set the proper client name.
  • In line 14, set the package name your app uses.
  • In line 15, enter the digest of the credentials used to sign your app. (Read below to learn how to get the digest.)
<config xmlns="http://tail-f.com/ns/config/1.0">
    <profiles xmlns="https://curity.se/ns/conf/base">
        <profile>
            <id>token-service</id>            <type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
            <settings>
                <authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
                    <client-store>
                        <config-backed>
                            <client>
                                <id>client-name</id>                                <attestation>
                                    <android>
                                        <package-name>com.example.yourpackage</package-name>                                        <signature-digest>A3...4=</signature-digest>                                    </android>
                                </attestation>
                            </client>
                        </config-backed>
                    </client-store>
                </authorization-server>
            </settings>
        </profile>
    </profiles>
</config>

The configuration above can also be set using the admin UI. This follows the same approach as described in the Development tab. Instead of making changes to the attestation policy leave it at default. Attestation Policy

The attestation configuration on the client looks the same as for Development but with one very important difference. Make sure that Disable Attestation Validation is not enabled.

Production Client Attestation

Remember, you can also use the configuration CLI or RESTCONF API to set these settings.

Obtaining a Signing Key Digest

You can use the gradle task signingReport to obtain hashes of your signing keys. If you use the admin UI, you can paste the generated SHA-256 there. If you import configuration from XML or use CLI or RESTCONF API, you must first base64-encode the SHA-256 hash. Here's how to do it from the terminal:

$ ./gradlew signingReport
...
SHA-256: 67:60:CA:11:93:...:4A:7E:A0:A4
...
$ echo "67:60:CA:11:93:...:4A:7E:A0:A4" | xxd -r -p | base64
Z2DKE...9oKQ=

You now have the Server and a client configured to use the authentication API from an Android app.

Using the HAAPI model SDK in an Android app

Add the SDK dependency to your project. The SDK is available on Maven Central.

dependencies {
    ...
    implementation 'se.curity.identityserver:haapi.models.android.sdk:2.0.0'
}
<dependency>
  <groupId>se.curity.identityserver</groupId>
  <artifactId>haapi.models.android.sdk</artifactId>
  <version>2.0.0</version>
  <scope>provided</scope>
</dependency>

The SDK exposes two classes: HaapiManager and OauthTokenManager. They accept a configuration object and are responsible for communication with the Curity Identity Server. The HaapiManager exposes the following methods:

  • start
  • followLink
  • submitForm
  • close

These methods (apart from close) are used to call subsequent steps of the authentication flow. Each method returns a HaapiResponse response representation. It is a sealed interface, implementations of which represent concrete response objects from the authentication API.

The app should call the API until it eventually gets an OAuthAuthorizationResponseStep, which signals that the authentication has finished. The app can now obtain access, refresh, and ID tokens from the Curity Identity Server. This can be easily done using the OAuthTokenManager object's fetchAccessToken method.

The OAuthTokenManager also exposes a refreshAccessToken method for refreshing access tokens.

Below is a trivial example of calling the API with the SDK. Note that no additional workaround for Client Attestation or DPoP is needed. Thanks to the SDK, these are still used in communication with the API.

val haapiConfiguration = HaapiConfiguration(
        keyStoreAlias = "keyStore",
        clientId = "haapi-client",
        baseUri = URI.create("https://idsvr.example.com"),
        tokenEndpointUri = URI.create("https://idsvr.example.com/token"),
        authorizationEndpointUri = URI.create("https://idsvr.example.com/authorization"),
        appRedirect = "app://redirect",
        isAutoRedirect = true
    )

val haapiManager = HaapiManager(haapiConfiguration = haapiConfiguration)

val tokenManager = OAuthTokenManager(oauthTokenConfiguration = haapiConfiguration)

val haapiResult = haapiManager.start(
    authorizationParameters = OAuthAuthorizationParameters(scope = "openid profile")
).getOrNull()

if (haapiResult is OAuthAuthorizationResponseStep) {
        val tokenResponse = tokenManager.fetchAccessToken(authorizationCode = haapiResult.properties.code)
        if (tokenResponse is SuccessfulTokenResponse) {
            println("Access token: ${token.accessToken}")
        }
} else if (haapiResult != null) {
    println("Type of the response from API: ${haapiResult::class.java}")
}

SDK 2.1 Update

The SDK has been updated to provide alternative methods for client attestation. This enables older Android devices without signing key attestation hardware support to also use HAAPI. This requires the SDK objects to be created via a factory class using a builder pattern. For further information, see the Implementing HAAPI Fallback tutorial.

val dcrConfiguration = DcrConfiguration(
    templateClientId = configuration.dcrTemplateClientId!!,
    clientRegistrationEndpointUri = URI(configuration.dcrClientRegistrationEndpointUri),
    context = context
)

val dcrClientCredentials =
    ClientAuthenticationMethodConfiguration.SignedJwt.Asymmetric(
      clientKeyStore = deviceKeyStore,
      clientKeyStorePassword = deviceKeyStorePassword,
      alias = "rsa",
      algorithmIdentifier = ClientAuthenticationMethodConfiguration.SignedJwt.Asymmetric.AlgorithmIdentifier.RS256
    )

val accessor = accessorFactory
    .setDcrConfiguration(dcrConfiguration)
    .setClientAuthenticationMethodConfiguration(dcrClientCredentials)
    .create()

val haapiManager = accessor.haapiManager
val oauthTokenManager = accessor.oauthTokenManager

Conclusion

With the SDKs, you can start using the Authentication API in minutes without delving into the specific details of the API's security enhancements. Thanks to the model SDK, you can quickly focus on creating user interfaces.

To learn more about implementing clients that consume this API, have a look at the Authentication API documentation.

If you need help setting up the API or SDK, or have any other questions or comments, please do not hesitate to contact us.