Quickstart — Android#
This walkthrough gets a HAAPI-authenticated login working in a fresh Android app in roughly 15 minutes. It uses the UI Layer (identityserver.haapi.android.ui.widget) because prebuilt fragments are the fastest path to a working flow. If your app needs custom screens, start at the layer chooser instead.
Before You Start#
You’ll need:
- Android Studio and an Android 8.0+ (API 26+) target.
- A Curity Identity Server with a HAAPI-capable OAuth client configured. You’ll need the
clientId, base URI, token endpoint, authorization endpoint, and redirect URI for that client. - An Android device or emulator. If attestation isn’t available on the target device (older Android version, non-Google-certified hardware, emulator), configure DCR fallback (covered in step 6).
Step 1 — Install the Framework#
Add the UIWidget dependency to app/build.gradle:
dependencies {
// Replace LATEST_VERSION with the latest release on Maven Central.
implementation("se.curity.identityserver:identityserver.haapi.android.ui.widget:LATEST_VERSION")
}
The SDK and Driver are pulled in transitively. Your app theme must extend Theme.Haapi.Ui.Widget.BaseTheme or Theme.MaterialComponents:
<style name="AppTheme" parent="@style/Theme.Haapi.Ui.Widget.BaseTheme" />
Step 2 — Implement HaapiUIWidgetApplication#
Create an Application subclass that exposes a WidgetConfiguration:
class ClientApplication : Application(), HaapiUIWidgetApplication {
private val baseUri = URI.create("https://idsvr.example.com")
override val widgetConfiguration: WidgetConfiguration
get() = WidgetConfiguration.Builder(
clientId = "my-haapi-client",
baseUri = baseUri,
tokenEndpointUri = baseUri.resolve("/oauth/v2/oauth-token"),
authorizationEndpointUri = baseUri.resolve("/oauth/v2/oauth-authorize"),
appRedirect = "app://haapi"
).build()
}
This is the same pattern documented in Configuration — your final code should match.
Step 3 — Register in the Manifest#
In AndroidManifest.xml, register your Application subclass and HaapiFlowActivity:
<application
android:name=".ClientApplication"
android:theme="@style/AppTheme"
...>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="se.curity.identityserver.haapi.android.ui.widget.HaapiFlowActivity" />
</application>
Step 4 — Launch the Flow#
In your MainActivity, structure the launch + result handling with named methods (signInTapped(), handleToken(), handleError()) so the start site is obvious. The launcher property must live at class scope because registerForActivityResult must be called before the activity reaches STARTED.
class MainActivity : AppCompatActivity() {
// Start the flow on user action
private fun signInTapped() {
startHaapiActivityForResult.launch(HaapiFlowActivity.newIntent(this))
}
// Persist the tokens (EncryptedSharedPreferences) and navigate
private fun handleToken(model: OauthModel.Token) {
// REMOVE this log BEFORE SHIPPING — token-bearing logs are a leak vector.
Log.d("HAAPI", "Got access token")
// TokenStore.save(model)
// navigateToHome()
}
// Show a user-facing error after a flow-level failure
private fun handleError(message: String) {
Log.e("HAAPI", "Authentication failed: $message")
}
// Launcher + result callback at property scope
private val startHaapiActivityForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { activityResult ->
when (activityResult.resultCode) {
RESULT_OK -> {
when (val model = activityResult.data
?.getParcelableExtra(HaapiFlowActivity.className) as? OauthModel) {
is OauthModel.Token -> handleToken(model)
is OauthModel.Error -> handleError(model.errorDescription ?: "Unknown error")
else -> handleError("Unexpected result")
}
}
RESULT_CANCELED -> {
// User dismissed the flow — no further action needed
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.sign_in_button).setOnClickListener { signInTapped() }
}
}
This is the same shape documented in Flow Lifecycle — your final code should match.
Step 5 — Set Up Digital Asset Links#
Before the framework can attest, your app’s package name and SHA-256 signing-certificate digest must be registered on the OAuth client on the Curity Identity Server. Get the digest by running:
./gradlew signingReport
Copy the SHA-256 line from the variant you ship (debug while developing, release for production) — it looks like AC:8A:65:9D:EC:.... Paste both the package name and the SHA-256 digest into your OAuth client’s HAAPI / attestation configuration on the Curity Identity Server.
Then add the asset-links meta-data to your manifest’s <application> element:
<application ...>
<meta-data
android:name="asset_statements"
android:resource="@string/asset_statements" />
</application>
And the string in res/values/strings.xml:
<string name="asset_statements" translatable="false">
[{\"include\": \"https://idsvr.example.com/.well-known/assetlinks.json\"}]
</string>
This ties your app’s package and signature to the server, satisfying the attestation check.
Step 6 — DCR Fallback for Untrusted Devices (Optional)#
When the device doesn’t support hardware-backed Key Attestation, or when the server rejects the attestation result (non-Google-certified hardware, older Android versions, emulators), configure DCR fallback in step 2:
override val widgetConfiguration: WidgetConfiguration
get() = WidgetConfiguration.Builder(
clientId = "my-haapi-client",
baseUri = baseUri,
tokenEndpointUri = baseUri.resolve("/oauth/v2/oauth-token"),
authorizationEndpointUri = baseUri.resolve("/oauth/v2/oauth-authorize"),
appRedirect = "app://haapi"
)
.setClientAuthenticationMethod(
WidgetConfiguration.ClientAuthenticationMethod.Secret("dev-secret")
)
.setDcr(
WidgetConfiguration.Dcr(
templateClientId = "dcr-template-client-haapi-android",
clientRegistrationEndpointUri = baseUri.resolve("/oauth/oauth-registration"),
context = this
)
)
.build()
The server must have a matching template client — see DCR for the server-side prerequisites.
Run It#
Build and run. Tap your sign-in button; HaapiFlowActivity presents the login screen, walks the user through the authentication flow, and returns an OauthModel.Token in the Activity Result.
Next: Theming to brand the prebuilt screens · Presentation Options to tune modal vs stack and polling cadence · Android UIWidget for the full topic list