/images/resources/tutorials/haapi/haapi-mobile.png

Implementing HAAPI Fallback

On this page

Intro

When using the hypermedia authentication API (HAAPI), the mobile client proves its identity to the Curity Identity Server before allowing authentication to begin. In production environments this is done using mobile attestation techniques, to obtain a certificate that asserts the app's identity, and includes its package name and signing certificate. After verification, the Curity Identity Server issues an authentication access token (AAT) with a haapi scope, which is then used for every API request during the authentication process.

To use mobile attestation, hardware support is required, which may not be present in certain Android devices, such as those originally released before Android 8.0. The Mobile Fallback Attestation article explains the design for using secure alternative methods to attest the client identity. This tutorial will show how to implement this pattern using the Curity Identity Server.

Fallback Simulation

To reproduce failed signing key based attestation you can use an emulator and configure production-level attestation:

Enable Production Attestation

Emulators lack the hardware support needed for production attestation, so this will lead to the following error:

Attestation Error

Fallback Configuration

This section will describe how to configure the Curity Identity Server to support fallback attestation when needed. Instances of the mobile app running on non-compliant devices will then prove their identity based on an alternative client credential, resulting in the creation of a dynamic client.

Add an Authentication Method

The quickest way to get an end-to-end solution working is to start with the configuration from the code example, then add a client credential that will be used when Dynamic Client Registration needs to be performed. Start with a simple fixed string secret, though this is not the most secure option and should be considered a stage 1 solution.

Enable DCR

Next, navigate to Profiles -> Token Service -> Dynamic Client Registration and enable Templatized option, which provides the best management options for mobile apps.

Add a Template Client

Then add a template client called haapi-template-client, with the Code Flow and HAAPI capabilities, or import the following XML. The OAuth settings such as scopes must match those of the main HAAPI client. Also ensure that the allow-without-attestation value is set to true.

xml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
<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>haapi-template-client</id>
<client-name>haapi-template-client</client-name>
<redirect-uris>app://haapi</redirect-uris>
<proof-key>
<require-proof-key>false</require-proof-key>
</proof-key>
<refresh-token-ttl>3600</refresh-token-ttl>
<audience>haapi-client</audience>
<scope>address</scope>
<scope>email</scope>
<scope>openid</scope>
<scope>phone</scope>
<scope>profile</scope>
<user-authentication>
</user-authentication>
<capabilities>
<code>
</code>
<haapi>
<allow-without-attestation>true</allow-without-attestation>
</haapi>
</capabilities>
<dynamic-client-registration-template>
<secret/>
<authenticate-client-by>haapi-android-client</authenticate-client-by>
</dynamic-client-registration-template>
<use-pairwise-subject-identifiers>
<sector-identifier>haapi-template-client</sector-identifier>
</use-pairwise-subject-identifiers>
<validate-port-on-loopback-interfaces>true</validate-port-on-loopback-interfaces>
</client>
</config-backed>
</client-store>
</authorization-server>
</settings>
</profile>
</profiles>
</config>

Android Code Updates

This section explains the basics of how to update code in your Android app, to use dynamic client registration (DCR) fallback attestation in your mobile app, when using the HAAPI UI SDK.

To implement DCR fallback, invoke the setDcr and setClientAuthentiationMethod methods on the configuration builder. The first of these will use the template client's ID and the DCR endpoint of the Curity Identity Server. When first getting fallback attestation working, start with a plain password as in the following code snippet.

kotlin
1234567891011121314151617181920212223242526272829303132
class DemoApplication: Application(), HaapiUIWidgetApplication {
private val baseUri = URI("https://login.example.com")
override val widgetConfiguration: WidgetConfiguration =
WidgetConfiguration.Builder(
clientId = "haapi-android-ui-client",
baseUri = baseUri,
tokenEndpointUri = baseUri.resolve("/oauth/v2/oauth-token"),
authorizationEndpointUri = baseUri.resolve("/oauth/v2/oauth-authorize"),
appRedirect = "app://haapi"
)
.setPresentationMode(PresentationMode.MODAL)
.setShouldAutoHandleFlowErrorFeedback(true)
.setAutoPollingDuration(Duration.ofSeconds(10))
.setOauthAuthorizationParamsProvider {
WidgetConfiguration.OAuthAuthorizationParams(
scope = listOf("openid", "profile")
)
}
.setDcr(
WidgetConfiguration.Dcr(
"haapi-template-client",
baseUri.resolve("/token-service/oauth-registration"),
this)
)
.setClientAuthentiationMethod(
WidgetConfiguration.ClientAuthenticationMethod.Secret("Password1")
)
.build()
}

SDK Behavior

The first time this code runs, the SDK will try to perform client attestation in the most secure way. If this is not possible then the SDK will fallback to registering a dynamic client, using the credential provided. This will result in a dynamic client ID and client secret that the SDK persists in mobile storage. This dynamic client ID and secret will then be used to get the HAAPI access token.

On all subsequent authentication attempts, the application will continue to create objects in the above manner, and does not need to be aware of the dynamic client ID being used. The SDK will detect that there is already a dynamic client ID and secret stored on this device, and will again use them to create the HAAPI access token. In both cases the HAAPI flow will then proceed in the normal way, using the access token to protect each hypermedia API request.

Strong Credentials

Once the process for implementing DCR fallback is understood, the next step is to provide strong client credentials based on public key infrastructure (PKI). It is recommended to use one of the options described in the following sections, though other solutions can also be designed.

The strong credential solutions explained below also support distinct client keys per device, which is recommended for the highest security. Popular ways of enabling this are to use a Mobile Device Management (MDM) system or a Software Attestation Framework.

Using Client Certificates

Attesting the client identity via a client certificate requires the use of transport level security (TLS). The process for sending client certificates from Android code is to update the client authentication method to represent a client certificate, by supplying the following properties:

FieldDescription
deviceKeyStoreAn Android keystore containing a client certificate to be sent in the Mutual TLS connection to the Identity Server
deviceKeyStorePasswordThe password needed to access the client certificate's key, if applicable
serverTrustStoreAn Android keystore referencing the root authority of the Identity Server's SSL certificate

Standard Android keystore technology would then be used, to load keystores and supply them to the SDK. The server trust store could be a BKS file deployed with the Android app, whereas the client certificate could be a separate keystore that has been populated by a third party system.

kotlin
12345
WidgetConfiguration.ClientAuthenticationMethod.Mtls(
clientKeyStore = deviceKeyStore,
clientKeyStorePassword = deviceKeyStorePassword,
serverTrustStore = serverTrustStore
)

The Curity Identity Server will first need to be updated to allow Mutual TLS on the token endpoint and the registration endpoint. The HAAPI client's authentication method must then be set to Mutual TLS, and configured to trust a root certificate authority from the issuing third party:

Mutual TLS Configuration

Using Client Assertions

Alternatively, message level security can be used, which is sometimes easier to manage. This is achieved by sending a JWT client assertion from each device. Firstly this requires similar changes to the client authentication method, to supply these values:

FieldDescription
deviceKeyStoreAn Android keystore containing a client key with which to sign a client assertion
deviceKeyStorePasswordThe password needed to access the client key, if applicable

An Android keystore would again be used, to load the client keystore and supply the key. When required, the SDK will do the work of producing the JWT client assertion and sending it to the Identity Server.

kotlin
123456
WidgetConfiguration.ClientAuthenticationMethod.SignedJwt.Asymmetric(
clientKeyStore = deviceKeyStore,
clientKeyStorePassword = deviceKeyStorePassword,
alias = "rsa",
algorithmIdentifier = WidgetConfiguration.ClientAuthenticationMethod.SignedJwt.Asymmetric.AlgorithmIdentifier.RS256
)

First ensure that the Curity Identity Server is configured to allow use of JWT assertions for client authentication, and that asymmetric algorithms include that specified in the Android code:

Assertion Algorithms

Next update the Curity Identity Server to point to a JWKS URI that provides a JSON Web Key Set (JWKS) containing trusted public keys in a JWK format. This URL can be provided via a simple API that manages public keys, or could potentially be an endpoint provided by a third party system. This mechanism enables distinct keys to be used to sign client assertions, which all belong to the same keyset.

JWKS URI Configuration

Crypto on Development Computers

Running an end-to-end Android and Curity Identity Server setup is a little tricky for developers. The mobile deployments repo therefore has some Android Crypto Resources. The README file explains how to use OpenSSL to issue keys and certificates for development. Public keys can then be configured in the Curity Identity Server. Private keys can be loaded from keystores into the Android code example, then used as the basis for Mutual TLS or Client Assertion credentials.

Multi-User Behavior

Although Android apps reference DCR related details when creating HAAPI objects, the SDK always tries to use the most secure attestation, by verifying the app's production certificate details. This ensures that the majority of users will continue to use the highest security. Only when this fails does the SDK fallback to using the configured DCR details. The small percentage of users with non-compliant devices will then use HAAPI based logins via DCR fallback, but with the same user experience.

Client Assurance Level

Once authentication completes, a client assurance level (cal) authentication attribute is available, to represent the strength with which the client app's identity was proven. This is illustrated below using a Debug Authentication Action. If required, a token procedure can be used to include this claim in access tokens. An API could then use it to deny access to sensitive operations unless strong attestation was used:

Client Assurance Level

Conclusion

This tutorial showed how to update code in a HAAPI secured Android app, to support DCR fallback. This ensures that non-compliant devices can be identified in a secure way before authentication begins. For the most secure DCR fallback attestation, it is recommended to use strong credentials, with distinct client keys per device.