# EUDI Developer Guide: Secure EUDI Wallet Integration for Mobile Developers (ARF, DCQL, 2026-2027)

You're building a mobile app that accepts EU digital identity credentials. The user taps "Login with EU Wallet." Their EUDI Wallet presents a signed credential, and your backend verifies it. Simple protocol, but the mobile runtime around it is an attack surface the [ARF spec](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework) doesn't cover. Let's be honest: trying to protect this cryptographic perfection from post-install rooting and Frida hooks is the exact reason mobile security engineers drink so much coffee. This guide shows how to ensure security of the full flow with [Talsec](https://www.talsec.app/) app protection tools: RASP+ SDK & AppiCrypt.

<figure><img src="/files/cRESxcAnvZcRvz47HGK6" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
This multi-part series is built for CTOs, architects, and mobile developers. We break down the regulatory clock, the architectural shifts, and the hands-on implementation details required to close the mobile security gap.

**Explore the complete series:**

* **Part 1:** [Build a Secure EUDI Wallet Relying Party](/appsec-articles/articles/build-a-secure-eudi-wallet-relying-party.md) An overview of the new eIDAS 2.0 landscape, the mobile threat surface nobody warned you about, and how to map regulatory obligations to your security stack.
* **Part 2:** [EUDI Wallet Integration: A CTO's Decision Guide](/appsec-articles/articles/eudi-wallet-integration-a-ctos-decision-guide.md) A strategic roadmap covering implementation timelines, the shift from real-time IdP callbacks to offline verification, and the privacy requirements your DPO needs you to know.
* **Part 3:** [Secure EUDI Wallet Integration for Mobile Developers](/appsec-articles/articles/eudi-developer-guide-secure-eudi-wallet-integration-for-mobile-developers-arf-dcql-2026-2027.md) A hands-on implementation guide for securing the OpenID4VP flow, writing GDPR-compliant DCQL queries, and setting up robust backend verification gates.
* **Part 4:** [App Attestation for EUDI Relying Parties](/appsec-articles/articles/eudi-app-attestation-choices-appicrypt-vs.-google-play-integrity-and-apple-app-attest.md) A deep dive into platform attestation: AppiCrypt, Google Play Integrity and Apple App Attest , and how to build a resilient, cross-platform attestation strategy.

*Disclaimer for full transparency: This article utilizes Talsec technology. We know we aren't the only vendor in the mobile security space. But we are the one running on 2,000,000,000 devices. We’ve protected more apps than there are cars on Earth. It's safe to say we know what we're doing.*
{% endhint %}

## Architecture

Your app is a **Relying Party (RP)**: you request credentials from the user's **EUDI Wallet** using [OpenID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html), receive signed SD-JWT presentations, and verify them offline against the [EU Trusted Lists](https://esignature.ec.europa.eu/efda/tl-browser/). The credential was signed by an EU Member State issuer; your backend never calls home to that issuer during verification.

<figure><img src="/files/M4u53gStjUV9FDD7mVvj" alt=""><figcaption></figcaption></figure>

That offline verification model is powerful, but it creates a gap: **a stolen credential from a compromised device passes every cryptographic check**. The [ARF spec](https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework) defines the credential format and protocol, but won't resolve the runtime device security for you. That gap is yours to close.

The Talsec security layer closes it by adding runtime proofs to every presentation flow, app authenticity and runtime integrity, before you ever parse the credential.

<figure><img src="/files/THa5WnW2x71IExbMbF7p" alt=""><figcaption></figcaption></figure>

## Step 1: Request Credentials (DCQL Query)

Use [DCQL](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) (the current OpenID4VP 1.0 query format) to specify exactly what you need. Request **minimum attributes**; this is a GDPR requirement.

```json
{
	"dcql_query": {
		"credentials": [
			{
				"id": "pid",
				"format": "dc+sd-jwt",
				"meta": { "vct_values": ["eu.europa.ec.eudi.pid.1"] },
				"claims": [
					{ "path": ["given_name"] },
					{ "path": ["family_name"] },
					{ "path": ["age_over_18"] }
				]
			}
		]
	}
}
```

For age verification only, request just `age_over_18`, the wallet that proves the claim without revealing the birth date.

## Step 2: Secure the Client Request

Before calling the EUDI Wallet, your app generates an [AppiCrypt®](https://docs.talsec.app/premium-products/product/appicrypt) token (cryptogram) and attaches it to the request. [As it's mentioned here](https://medium.com/@talsec/techtalk-predictive-apps-protection-with-sergiy-yakymchuk-talsec-6470284b6361), the cryptogram is a "cryptographical snapshot" of the device's security state, verified on the backend in real time to detect threats such as debuggers or simulators, and it proves the app binary is genuine and not repackaged.

Let's see some sample codes to understand a bit better the pattern, start with Android (Kotlin):

```kotlin
data class SecureRequest(
    val openid4vp: OpenID4VPRequest,
    val cryptogram: String,  // cryptographical snapshot of app + device security state
)

suspend fun buildSecureRequest(): SecureRequest {
    val nonce = secureNonce()
    val req = OpenID4VPRequest(
        dcqlQuery = pidAgeVerification(),
        nonce = nonce,
        clientId = RP_CLIENT_ID,
        responseUri = "$BACKEND_URL/presentation/callback",
    )

    return SecureRequest(
        openid4vp = req,
        cryptogram = Talsec.getCryptogram(req.canonicalBytes()),
    )
}
```

Similarly we can repeat that for iOS (Swift):

```swift
struct SecureRequest: Encodable {
    let openid4vp: OpenID4VPRequest
    let cryptogram: String  // cryptographical snapshot of app + device security state
}

func buildSecureRequest() async throws -> SecureRequest {
    let nonce = secureNonce()
    let req = OpenID4VPRequest(
        dcqlQuery: pidAgeVerification(),
        nonce: nonce,
        clientId: RP_CLIENT_ID,
        responseUri: "\(BACKEND_URL)/presentation/callback"
    )

    return SecureRequest(
        openid4vp: req,
        cryptogram: try Talsec.getCryptogram(req.canonicalBytes())
    )
}
```

What the `cryptogram` token gives your backend:

* **App authenticity:** A repackaged or cloned APK/IPA cannot produce a valid cryptogram for your App ID ([AppiCrypt docs](https://docs.talsec.app/premium-products/product/appicrypt))
* **Runtime threat flags:** root/jailbreak (su, Magisk, palera1n), hooking frameworks (Frida/Xposed), emulators, debuggers, all captured in the same cryptogram
* **Real-time risk score:** the backend script verifies the cryptogram and returns an integrity status per request

For on-device runtime protection, use [freeRASP](https://github.com/talsec/Free-RASP-Community) (open-source) or [RASP+](https://www.talsec.app/rasp), which adds overlay detection, screen-mirroring defense, and accessibility-abuse monitoring.

{% hint style="info" %}
**Device Risk State API:** Talsec also offers a server-side Device Risk State API, a product that lets your backend check a device's security state on-demand, independent of the user's current app session. See [plans comparison](https://www.talsec.app/plans-comparison) or contact [Talsec](https://talsec.app) directly for access and API details.
{% endhint %}

## Step 3: Verify on the Server

When the mobile app sends a `SecureRequest`, your backend runs several checks before reading any credential claims.

The ordering matters: cheap checks first, expensive ones last. Here is how you can create your gates in each step in your framework of choice for the backend:

1. **App authenticity (AppiCrypt® backend script) verification:** Decrypt and verify the AppiCrypt cryptogram using the simple script Talsec provides. The cryptogram is **bound to your App instance**, and the request payload, **a repackaged or cloned APK/IPA, cannot produce a valid one**. *REJECT 403 if invalid.*
2. **Runtime integrity (RASP flags inside the cryptogram):** The AppiCrypt cryptogram also carries RASP threat flags captured on the device. Check: no root/jailbreak, no hooking framework (Frida/Xposed/Substrate), no debugger, no emulator. Tune the policy per your risk appetite. *REJECT 403 if flags exceed policy threshold.*

```typescript
async function handleProtectedRequest(req: Request) {
  const cryptogram = req.headers["appicrypt"];
  if (!cryptogram) return unauthorized(401);

  appicryptEvaluate(cryptogram)
  // 🪄Checks performed by AppiCrypt automatically🪄:
  // - compute threat checks and evaluate RASP flags
  // - verify signature
  // - validate app/policy info against backend config
  // - compare nonce
  // ...

  if (overallAssessment !== "OK") return forbidden(403);
  if (riskScore > securityThreshold) return forbidden(403);

  return forwardToBusinessLogic(req);
}
```

1. **Credential verification (ARF-compliant, any OpenID4VP library)**
   * Issuer key must chain to the EU Trusted List (offline, no issuer callback)
   * nonce must match what you sent (prevents presentation replay)
   * aud must match your RP Client ID (prevents cross-RP reuse)
   * key-binding JWT must be valid (proves wallet holds the key) → REJECT 403 if any check fails
2. **Revocation Check (Token Status List / OCSP):** Fetch the Token Status List bitstring from the issuer CDN. Confirm the credential's index is 0 (active). *REJECT 403 if revoked.*

Here is what it looks like in a simplified image:

<figure><img src="/files/6uJlwwtpratahDxXgKYX" alt=""><figcaption></figcaption></figure>

## Privacy & Operational Rules

I find some gaps when working with EUDI wallets, and I thought to bring them here. Three rules that prevent production incidents and GDPR fines:

**1. Check credential revocation (Token Status List):** Every SD-JWT contains a `status` claim pointing to a [Token Status List](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/). Fetch it, decode the bitstring, and confirm the credential index is still `0` (active). This is the most commonly skipped verification step; without it, you'll accept revoked credentials indefinitely.&#x20;

**2. Never log the raw `vp_token:`** The verifiable presentation contains disclosed PII (name, birth date, nationality). Logging it raw = [GDPR Article 5](https://gdpr-info.eu/art-5-gdpr/) violation. Log only: `issuer`, `vct`, `iat`, `exp`, and your internal session ID. If you need the payload for debugging, store it encrypted with a short TTL in a separate audit store.

**3. Use pairwise identifiers for user storage:** Don't store the wallet's `sub` claim directly; it's the same across all RPs the user interacts with, enabling cross-service tracking. Instead, derive a per-RP identifier:

```
val pairwiseId = hmacSha256(key = RP_DOMAIN_SECRET, data = presentation.sub)
// Store pairwiseId as your user reference, can't be correlated across RPs
```

## Integration Checklist

Use this as your PR/release todolist. Every item maps to a security control described above; skip one, and you have a gap.&#x20;

* Register as RP in the Member State trust ecosystem
* Integrate Talsec SDK, AppiCrypt + RASP+ (Android / iOS platforms)
* Backend: verify AppiCrypt cryptogram on every sensitive request (covers app authenticity + RASP threat flags in one step)
* Build DCQL queries, minimum attributes per use case
* Verify [SD-JWT](https://www.rfc-editor.org/rfc/rfc9901.html): issuer signature → disclosure hashes → **KB-JWT** (nonce + aud + sd\_hash)
* Check credential revocation via [Token Status List](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/) on every presentation
* Fetch and cache [EU Trusted Lists](https://esignature.ec.europa.eu/efda/tl-browser/); validate issuer cert chain
* Define risk policy: fail-closed on AppiCrypt violation, step-up on borderline scores
* Per-request nonce bound to your RP `client_id`
* Never log raw `vp_token`, log only metadata (issuer, vct, iat, session ID)
* Store pairwise-derived user IDs, not raw wallet `sub` claims

## Conclusion

The EUDI Wallet shifts identity verification from server-mediated flows to cryptographic offline verification. That's powerful, but it moves the trust boundary to the mobile device. If the device is compromised, the credential is compromised. Start with the checklist above. Ship iteratively: RASP+ first, then upgrade to AppiCrypt and Malware Detection as your threat exposure grows. Happy coding!

*written by Majid Hajian*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.talsec.app/appsec-articles/articles/eudi-developer-guide-secure-eudi-wallet-integration-for-mobile-developers-arf-dcql-2026-2027.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
