Upgrade — Android UIWidget Flow Launch and Redirect URI#
From 5.8.0, HaapiFlowActivity is not exported: it runs only when your app starts it for a result, and accepts only the configured redirect.
Every app that uses the Android UIWidget must declare its redirect URI to build against 5.8.0; the other steps depend on how the app launches and declares the flow screen.
What Changed#
| Before | After |
|---|---|
HaapiFlowActivity was exported | Not exported; runs only when your app starts it for a result |
HaapiFlowActivity declared the redirect intent filter from the hui_config_deep_link_scheme and hui_config_deep_link_host strings, default app and haapi | A redirect entry point declares it from the required huiRedirectScheme and huiRedirectHost manifest placeholders; the strings are removed |
| Any URI delivered to the flow during a browser step was submitted | Only a redirect whose scheme, host, port, and path equal appRedirect is submitted |
Launch with HaapiFlowActivity.newIntent and read the result extras under HaapiFlowActivity.className | HaapiFlowActivity.Contract returns a typed HaapiFlowResult; newIntent and className are deprecated and will be removed in a future major version |
Migration#
1. Declare the Redirect URI#
Set the scheme and host of appRedirect as manifest placeholders in the app module:
// app/build.gradle.kts
android {
defaultConfig {
manifestPlaceholders["huiRedirectScheme"] = "com.example.app"
manifestPlaceholders["huiRedirectHost"] = "haapi"
}
}// app/build.gradle
android {
defaultConfig {
manifestPlaceholders += [huiRedirectScheme: "com.example.app", huiRedirectHost: "haapi"]
}
}- You overrode
hui_config_deep_link_schemeandhui_config_deep_link_host: move their values into the placeholders and delete the overrides. - You kept the defaults: your redirect is
app://haapi. DeclaringhuiRedirectScheme: "app"andhuiRedirectHost: "haapi"keeps the app working, but any app can register that scheme. Plan the move to an App Link or a reverse-domain scheme in step 4. - You override
dataMappersFactory: passappRedirectas theredirectToof yourHaapiDataMappersFactory. A browser step now resumes only on a redirect that matchesappRedirect.
The placeholders, appRedirect, a redirect URI registered on the OAuth client, and any custom redirectTo must name the same URI:
| Problem | Symptom |
|---|---|
| The placeholders are missing | The build fails the manifest merge with requires a placeholder substitution but no value for <huiRedirectScheme> is provided |
The client does not register appRedirect | The flow fails with the server’s redirect_uri error |
The placeholders do not match appRedirect | The browser step never returns to the app. With logging enabled, the framework logs a warning when the flow starts |
A custom HaapiDataMappersFactory passes a redirectTo other than appRedirect | The flow ignores the redirect and the browser step never completes. With logging enabled, the framework logs a warning when the redirect arrives |
2. Remove Manifest Declarations of HaapiFlowActivity#
The UIWidget’s manifest declares HaapiFlowActivity; your app does not need to. If your manifest declares it:
- With
android:exported="true": remove the attribute. The build otherwise fails the manifest merge withAttribute activity#se.curity.identityserver.haapi.android.ui.widget.HaapiFlowActivity@exported value=(true) … is also present at … value=(false). Do not apply thetools:replace="android:exported"that the error suggests: it exposes the flow screen to other apps again. - With intent filters: remove them. The placeholders from step 1 replace them, and redirects no longer reach the flow through them.
- With only
android:theme: keep it. A theme-only declaration still works.
3. Start the Flow for a Result#
HaapiFlowActivity now runs only when your app starts it for a result. Started with startActivity, or without a result request, it finishes with RESULT_CANCELED before making any request.
Replace the generic contract and the extras lookup with HaapiFlowActivity.Contract:
// Before
private val startHaapiActivityForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { activityResult ->
if (activityResult.resultCode == RESULT_OK) {
when (val model = activityResult.data
?.getParcelableExtra(HaapiFlowActivity.className) as? OauthModel) {
is OauthModel.Token -> handleToken(model)
is OauthModel.Error -> handleError(model.error)
else -> Unit
}
}
}
startHaapiActivityForResult.launch(HaapiFlowActivity.newIntent(this))
// After
private val startHaapiFlow = registerForActivityResult(HaapiFlowActivity.Contract()) { result ->
when (result) {
is HaapiFlowResult.Completed -> when (val model = result.oauthModel) {
is OauthModel.Token -> handleToken(model)
is OauthModel.Error -> handleError(model.error)
}
// cause is null when the user left the flow
is HaapiFlowResult.Cancelled -> result.cause?.let { handleError("Interrupted: ${it.message}") }
}
}
startHaapiFlow.launch()
launch() without an argument is the androidx.activity.result.launch extension; from Java, call launch(null). HaapiFlowActivity.newIntent and HaapiFlowActivity.className keep working when the flow is started for a result, and will be removed in a future major version.
4. Move to a Verified App Link or a Reverse-Domain Scheme#
Android recommends verified App Links: Android delivers a verified https link only to the app that the domain names. The Curity Identity Server already publishes /.well-known/assetlinks.json for the Android apps configured on its clients, with the delegate_permission/common.handle_all_urls relation, so the redirect can live on the server’s own host:
-
Register a redirect URI on a path the server does not serve, such as
https://idsvr.example.com/haapi/callback, on the OAuth client. -
Set the placeholders to
httpsand the server host, for examplemanifestPlaceholders += [huiRedirectScheme: "https", huiRedirectHost: "idsvr.example.com"]. -
Limit the path, so the app does not claim every URL of the server host, in
res/values/strings.xml:<string name="hui_redirect_path_prefix" translatable="false">/haapi/callback</string> -
Set
appRedirect = "https://idsvr.example.com/haapi/callback". -
Check the verification state on a device with
adb shell pm get-app-links com.example.app.
Verification needs a publicly resolvable host serving assetlinks.json over https with a publicly trusted certificate. Where that is not possible, use a private-use scheme based on a domain you control, in reverse order, such as com.example.app://haapi, and keep the default path prefix.
Either way, register the new redirect URI on the OAuth client before releasing the app, and keep the old one registered until no installed version of the app still uses it. Redirect URI covers both formats, their requirements, and what each protects against.
Verify#
- The app builds. A missing placeholder or an exported declaration fails the manifest merge, as described above.
- A login that runs a browser step, such as a federated login, returns to the app and completes.
- With logging enabled, the framework logs no redirect warning when the flow starts. See Logging.
Related: Configuration (UI Layer) · Flow Lifecycle · Android Platform Notes