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

Implementing HAAPI Attestation 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 Attestation Fallback article explains the design for using secure alternative methods to prove 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 the Curity Identity Server with production-level attestation. This can be done from the facilities menu of the Admin UI. (FacilitiesClient AttestationAndroid Policies)

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 attestation fallback 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 Android HAAPI code example. Then edit the haapi-android-client settings and configure client authentication. This will provide a client credential that is used by non-compliant devices, when they fall back to dynamic client registration. 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 ProfilesToken ServiceDynamic Registration and use the wizard to enable the Templatized option.

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>

Remember to Commit any configuration changes.

Android Code Updates

This section explains how to configure the HAAPI SDK in your Android app, to enable dynamic client registration (DCR) attestation fallback. Do so by adding the setDcr and setClientAuthentiationMethod methods on the configuration builder. The first of these provides the template client's ID and the DCR endpoint of the Curity Identity Server. When first getting the fallback working, start with a plain password, as in the following code snippet.

kotlin
123456789101112131415161718192021222324252627282930313232
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 fall back 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, it is recommended to provide strong client credentials based on public key infrastructure (PKI). Use one of the options described in the following sections. Other SDK options may be provided by Curity in the future. Where possible it is also recommended to use distinct client keys per device, for the highest security. One way to enable this is to use a Mobile Device Management (MDM) system.

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
deviceKeyStoreA keystore containing a client certificate sent in a Mutual TLS connection to the Curity Identity Server.
deviceKeyStorePasswordThe password needed to access the client certificate's key, if applicable.
serverTrustStoreA trust store referencing the root authority of the Curity 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. Go to ProfilesToken ServiceEndpoints, and set Client Authentication to Allow (the orange question mark) for these endpoints.

Enable mTLS Authentication

Then, set the HAAPI client's authentication method to Mutual TLS, and configure it to trust a root certificate authority from the issuing third party. Go to ProfilesToken ServiceClientshaapi-android-client and scroll down to Client Authentication in the General tab:

Mutual TLS Configuration

Using Client Assertions

Alternatively, message level security can be used. This is achieved by sending a JWT client assertion from each device. This requires similar changes to the client authentication method, to supply the following values:

FieldDescription
deviceKeyStoreA 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 loaded,to supply the key to the SDK, which will then do the work of producing the JWT client assertion and sending it to the Curity Identity Server. The SDK supports various asymmetric signature algorithms, and the mainstream RS256 option is used here.

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

Ensure that the Curity Identity Server is configured to allow use of JWT assertions for client authentication, and that the asymmetric algorithm specified in Android code is enabled:

Assertion Algorithms

Next, update the app's client configuration in the Curity Identity Server to use an asymmetric-key client authentication method. A public key must be configured, that is used to verify received client assertions. This must correspond to the full private key that is installed on devices, and which signs client assertions sent by the mobile app:

Asymmetric Verification 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 fall back 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.

Client Assurance Level Attribute

The possible values issued by the Curity Identity Server are summarized in the following table:

Client Assurance LevelDescription
by-policyThe user retrieved a HAAPI access token via signing key attestation, with the properties configured in the Curity Identity Server.
by-provisioned-keyThe user retrieved a HAAPI access token with credentials created via a client certificate or asymmmetric key.
noneThe user retrieved a HAAPI access token via credentials created with a client secret.

If required, a claim can be created to represent the assurance level, and issue it to tokens. To do so, first navigate to ProfilesToken ServiceScopesClaims Providers → and create an authentication context claims provider if required. Then use the token designer window to add the scope to access tokens or ID tokens issued to the app.

Client Assurance Level Claim

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, it is recommended to use strong credentials, with distinct client keys per device if possible.

Join our Newsletter

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

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial