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#

BeforeAfter
HaapiFlowActivity was exportedNot 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 haapiA 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 submittedOnly a redirect whose scheme, host, port, and path equal appRedirect is submitted
Launch with HaapiFlowActivity.newIntent and read the result extras under HaapiFlowActivity.classNameHaapiFlowActivity.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_scheme and hui_config_deep_link_host: move their values into the placeholders and delete the overrides.
  • You kept the defaults: your redirect is app://haapi. Declaring huiRedirectScheme: "app" and huiRedirectHost: "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: pass appRedirect as the redirectTo of your HaapiDataMappersFactory. A browser step now resumes only on a redirect that matches appRedirect.

The placeholders, appRedirect, a redirect URI registered on the OAuth client, and any custom redirectTo must name the same URI:

ProblemSymptom
The placeholders are missingThe build fails the manifest merge with requires a placeholder substitution but no value for <huiRedirectScheme> is provided
The client does not register appRedirectThe flow fails with the server’s redirect_uri error
The placeholders do not match appRedirectThe 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 appRedirectThe 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 with Attribute activity#se.curity.identityserver.haapi.android.ui.widget.HaapiFlowActivity@exported value=(true) … is also present at … value=(false). Do not apply the tools: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.

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:

  1. Register a redirect URI on a path the server does not serve, such as https://idsvr.example.com/haapi/callback, on the OAuth client.

  2. Set the placeholders to https and the server host, for example manifestPlaceholders += [huiRedirectScheme: "https", huiRedirectHost: "idsvr.example.com"].

  3. 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>
  4. Set appRedirect = "https://idsvr.example.com/haapi/callback".

  5. 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.

Was this helpful?