German eID (Personalausweis) + Criipto on Android: What I Broke, Fixed, and Finally Understood
A developer’s learning log integrating German Personalausweis eID with Criipto using OIDC, Custom Tabs, deep links, and txinfo. What worked, what didn’t, and how I made the app switch back automatically.
Table of contents
- Context: What I’m Building
- What I Learned / Architecture Decisions
- Key Concepts Explained
- Code Snippets & Scenarios
- A) Building the OIDC Request (Kotlin + AppAuth)
- B) Launching in Custom Tabs
- C) Android App Link for Automatic Return
- D) Handling the Redirect
- E) Fallback: Proving txinfo Is Missing Downstream
- What Worked, What Didn’t
- What I’d Do Differently
- References / Further Reading
Context: What I’m Building
I’m integrating German eID (Personalausweis) into an Android SDK using Criipto Verify as the OIDC broker. The goal: start the flow in-app (via Chrome Custom Tabs), launch AusweisApp2 on-device for verification, and return users back into my SDK automatically—no manual browser juggling.
Along the way, I ran into a deceptively simple error from the German test service (eid-test.de):
IDENT_CREATING_TRANSACTION_INFORMATION_FAILED
No txinfo parameter was provided by the user although a template string with a placeholder is present.
This post documents the questions I asked, how I validated assumptions, and the changes that made the flow stable across app → AusweisApp2 → app.
What I Learned / Architecture Decisions
- OpenID flow shape: My app talks to Criipto; Criipto brokers to the German eID test service. The return path after AusweisApp2 is eID service → Criipto → my redirect URI. If that last hop isn’t an app link I own and verify, Chrome reopens instead of my app.
txinfois mandatory when your Criipto configuration expects it. Sending it from Android is necessary—but not sufficient—unless Criipto whitelists/forwards it.- WebView is a dead end for mobile app handoff. Custom Tabs + OIDC is the right track.
- App Links (autoVerify) and a Criipto “appswitch” redirect are key to jump back into the app after the AusweisApp2 journey.
Key Concepts Explained
1) txinfo (Transaction Information)
German eID flows require the RP (via the broker) to declare what attributes to read from the ID card. That’s the txinfo JSON (often Base64-encoded) with keys like RequestedAttributes (e.g., GivenNames, FamilyNames, DateOfBirth). If Criipto’s scheme template references ${txinfo}, but the parameter isn’t forwarded, the German service complains.
2) Why my txinfo didn’t reach eid-test.de
I added txinfo to AuthorizationRequest.setAdditionalParameters(...) in Android. Criipto still didn’t pass it along. Root cause: Criipto must allow/whitelist txinfo for that scheme in my tenant. Without that, Criipto builds the downstream URL without txinfo, triggering the error above.
3) Custom Tabs vs. the system browser on return
When AusweisApp2 finishes, it returns to eid-test.de (browser context), then to Criipto, then to my redirect_uri. Unless that redirect URI is an Android App Link I control and is verified, Android may keep me in Chrome. With a verified App Link + Criipto “appswitch” redirect, the OS routes the final hop straight into my app.
Code Snippets & Scenarios
A) Building the OIDC Request (Kotlin + AppAuth)
val extras = mutableMapOf(
"acr_values" to scheme.acrValue
)
// Inject txinfo only for German Personalausweis
if (scheme.schemeId.equals("urn:grn:authn:de:personalausweis", ignoreCase = true)) {
val txInfoJson = """
{
"RequestedAttributes": [
"GivenNames",
"FamilyNames",
"DateOfBirth",
"PlaceOfResidence"
]
}
""".trimIndent()
val txInfoB64 = android.util.Base64.encodeToString(
txInfoJson.toByteArray(), android.util.Base64.NO_WRAP
)
extras["txinfo"] = txInfoB64
}
val request = AuthorizationRequest.Builder(
serviceConfig,
EidConfiguration.CLIENT_ID,
ResponseTypeValues.CODE,
EidConfiguration.redirectUri // e.g. https://cc-dev-flow-test.criipto.id/android/appswitch
)
.setScope("openid")
.setPrompt("login")
.setAdditionalParameters(extras)
.setLoginHint("appswitch:android appswitch:resumeUrl:${'$'}{EidConfiguration.appSwitchUri}")
.build()
Important: This only works once Criipto enables forwarding of txinfo for your German scheme. Otherwise the parameter is dropped server-side.
B) Launching in Custom Tabs
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(false)
.setShareState(CustomTabsIntent.SHARE_STATE_OFF)
.build()
val authIntent = authService.getAuthorizationRequestIntent(request, customTabsIntent)
startActivity(authIntent)
C) Android App Link for Automatic Return
Manifest
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="cc-dev-flow-test.criipto.id"
android:path="/android/appswitch" />
</intent-filter>
Make sure the same URI is registered under Redirect URIs in Criipto.
D) Handling the Redirect
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val uri = intent?.data ?: return
if (uri.toString().startsWith(EidConfiguration.redirectUri)) {
// AppAuth will parse the code, you continue with token exchange
AuthorizationService(this).performActionWithIntent(intent!!) { response, ex ->
if (ex != null) {
// handle error
} else if (response != null) {
// exchange code → tokens
}
}
}
}
E) Fallback: Proving txinfo Is Missing Downstream
When you land on Criipto’s hosted screen, you can inspect the data bootstrap (if exposed) or simply watch the URL Criipto builds for eid-test.de. If it lacks &txinfo=..., ask Criipto to enable/whitelist that parameter for your tenant & scheme.
What Worked, What Didn’t
Worked
- Switching from WebView to Custom Tabs for all mobile-app handoffs.
- Adding
txinfoin Android and getting Criipto to forward it. - Using Android App Links with
android:autoVerify="true"so the final redirect jumps into my app.
Didn’t Work
- Expecting WebView to launch native apps reliably via deep links.
- Assuming Criipto forwards arbitrary OIDC params by default—it doesn’t. Tenant configuration matters.
- Hoping AusweisApp2 → browser return would reuse the Custom Tab context automatically; Android often routes that via Chrome unless you finish with a verified App Link.
What I’d Do Differently
- Start with an integration map: Draw the exact hops (App → Criipto → eID → AusweisApp2 → eID → Criipto → App). It clarifies where each redirect actually lives.
- Confirm broker param forwarding early: Before UI, validate that
txinfosurvives from app to broker to eID service. - Automate deep link verification in CI (assetlinks.json checks), so my
android:autoVerifydoesn’t silently fail.
References / Further Reading
- Criipto Verify — German Personalausweis guide (search: Criipto Verify German Personalausweis)
- German eID / AusweisApp2 integration (search: AusweisApp2 Integration Guide txInfo RequestedAttributes)
- Android App Links: https://developer.android.com/training/app-links
- AppAuth for Android: https://github.com/openid/AppAuth-Android
Final takeaway: German eID on Android isn’t hard—it’s distributed. The trick is aligning three layers: (1) your Android deep links + Custom Tabs, (2) Criipto’s broker settings (forward txinfo, use your App Link as redirect), and (3) the eID service’s expectations. Once the parameters and redirects line up, the on-device flow feels native and reliable.
Share
More to explore
Keep exploring
1/11/2026
Kotlin to C/C++ Transition Guide: A Systems Programmer's Cheat Sheet
Preparing for a systems programming interview but haven't touched C/C++ since university? This guide bridges your Kotlin knowledge to C/C++ with side-by-side syntax comparisons, memory management deep dives, and critical undefined behaviors you need to know.
1/10/2026
The SDK Mindset: Why Your Code Isn't Your Own Anymore
A deep dive into the paradigm shift from application development to SDK design, and why building libraries requires a fundamentally different mental model
12/16/2025
Building a Frictionless Android Sample App: README Funnel, Doctor Script, and a CI Compatibility Matrix
My AI-assisted learning log on turning an Android SDK demo into a low-friction client experience: a decision-tree README, environment doctor scripts, and a GitHub Actions build matrix that generates a compatibility matrix.
Previous
Android Learning Log: Compose Permissions, SavedStateHandle, and Testing Pitfalls
Next