Version: v0.13
HTTP/WebSocket Transport
HTTP and WebSocket transports enable remote mDoc presentations over the network. These are used with TO_APP (reverse) engagement for online verification scenarios.
Use Cases
Remote transports are used for:
- Online identity verification during account registration
- Remote document verification for digital services
- Integration with OID4VP presentation flows
- Server-based verification without physical proximity
TO_APP Engagement with HTTP
The TO_APP engagement type uses HTTP-based retrieval:
- Android/Kotlin
- iOS/Swift
val engagementManager = session.component.mdocEngagementManager
// Handle incoming mdoc:// URI from verifier
fun handleIncomingUri(uri: String) {
lifecycleScope.launch {
val result = engagementManager.toApp(
mdocUri = uri,
autoStart = true
)
when (result) {
is IdkResult.Success -> {
val engagement = result.value
handleEngagement(engagement)
}
is IdkResult.Failure -> {
showError(result.error)
}
}
}
}
let engagementManager = session.component.mdocEngagementManager
// Handle incoming mdoc:// URI from verifier
func handleIncomingUri(uri: String) async {
let result = engagementManager.toApp(
mdocUri: uri,
autoStart: true
)
switch result {
case .success(let engagement):
await handleEngagement(engagement: engagement)
case .failure(let error):
showError(error: error)
}
}
URI Scheme Patterns
The toApp() method supports three URI patterns:
| Scheme | Transport | Description |
|---|---|---|
mdoc: (opaque) | BLE/NFC | Classic reverse engagement |
mdoc:// (hierarchical) | REST API | Website/server retrieval |
mdoc-openid4vp:// | OID4VP | OAuth 2.0 integration |
Protocol Flow
REST API Mode
Holder Verifier Server
│ │
│ 1. Parse mdoc:// URI │
│ │
│ 2. GET /request │
│────────────────────────────────────────►│
│ │
│ 3. 200 OK (DeviceRequest) │
│◄────────────────────────────────────────│
│ │
│ 4. User consent │
│ │
│ 5. POST /response (DeviceResponse) │
│────────────────────────────────────────►│
│ │
│ 6. 200 OK │
│◄────────────────────────────────────────│
Handling TO_APP Flow
Complete TO_APP flow with HTTP retrieval:
- Android/Kotlin
- iOS/Swift
class ToAppHandler(
private val engagementManager: MdocEngagementManager,
private val documentProvider: DocumentProvider
) {
suspend fun handleUri(uri: String) {
// Create engagement from URI
val result = engagementManager.toApp(
mdocUri = uri,
autoStart = false
)
when (result) {
is IdkResult.Success -> {
val engagement = result.value
// Monitor engagement events
engagement.events.collect { event ->
when (event.state) {
is MdocEngagementState.Connected -> {
// Start transfer
val transferManager = engagement.start()
// Handle the transfer
val deviceRequest = transferManager.receiveDeviceRequest()
val approved = showConsentDialog(deviceRequest)
if (approved) {
val response = transferManager.createResponse(
deviceRequest = deviceRequest,
documentProvider = documentProvider
)
transferManager.sendDeviceResponse(response)
}
}
}
}
}
is IdkResult.Failure -> {
handleError(result.error)
}
}
}
}
class ToAppHandler {
private let engagementManager: MdocEngagementManager
private let documentProvider: DocumentProvider
func handleUri(uri: String) async {
// Create engagement from URI
let result = engagementManager.toApp(
mdocUri: uri,
autoStart: false
)
switch result {
case .success(let engagement):
// Monitor engagement events
for await event in engagement.events {
switch event.state {
case .connected:
do {
// Start transfer
let transferManager = try await engagement.start()
// Handle the transfer
let deviceRequest = try await transferManager.receiveDeviceRequest()
let approved = await showConsentDialog(request: deviceRequest)
if approved {
let response = try await transferManager.createResponse(
deviceRequest: deviceRequest,
documentProvider: documentProvider
)
try await transferManager.sendDeviceResponse(deviceResponse: response)
}
} catch {
handleError(error: error)
}
default:
break
}
}
case .failure(let error):
handleError(error: error)
}
}
}
OID4VP Integration
For mdoc-openid4vp:// URIs, the IDK integrates with OID4VP:
- Android/Kotlin
- iOS/Swift
// OID4VP URIs are handled the same way
val uri = "mdoc-openid4vp://authorize?..."
val result = engagementManager.toApp(
mdocUri = uri,
autoStart = true
)
// The IDK handles the OID4VP protocol internally
// OID4VP URIs are handled the same way
let uri = "mdoc-openid4vp://authorize?..."
let result = engagementManager.toApp(
mdocUri: uri,
autoStart: true
)
// The IDK handles the OID4VP protocol internally
Deep Link Registration
Android
Register to handle mDoc URIs in AndroidManifest.xml:
<activity
android:name=".MdocHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mdoc" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mdoc-openid4vp" />
</intent-filter>
</activity>
iOS
Register URL schemes in Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>mdoc</string>
<string>mdoc-openid4vp</string>
</array>
</dict>
</array>
Monitoring Transfer State
Monitor HTTP-based transfer progress:
- Android/Kotlin
- iOS/Swift
engagementManager.eventHub.allEvents.collect { event ->
when (event) {
is MdocEngagementEvent -> {
when (event.state) {
is MdocEngagementState.Connecting -> {
showMessage("Connecting to verifier...")
}
is MdocEngagementState.Connected -> {
showMessage("Connected")
}
is MdocEngagementState.Transferring -> {
showMessage("Transferring credentials...")
}
is MdocEngagementState.Completed -> {
showMessage("Transfer complete")
}
is MdocEngagementState.Error -> {
val error = (event.state as MdocEngagementState.Error).error
showError("Error: ${error.message}")
}
}
}
}
}
for await event in engagementManager.eventHub.allEvents {
if let engagementEvent = event as? MdocEngagementEvent {
switch engagementEvent.state {
case .connecting:
showMessage(text: "Connecting to verifier...")
case .connected:
showMessage(text: "Connected")
case .transferring:
showMessage(text: "Transferring credentials...")
case .completed:
showMessage(text: "Transfer complete")
case .error(let error):
showError(text: "Error: \(error.message)")
default:
break
}
}
}
Error Handling
Handle network errors gracefully:
- Android/Kotlin
- iOS/Swift
when (result) {
is IdkResult.Failure -> {
val error = result.error
val message = when {
error.message.contains("timeout", ignoreCase = true) ->
"Connection timed out. Please try again."
error.message.contains("network", ignoreCase = true) ->
"Network error. Check your connection."
error.message.contains("ssl", ignoreCase = true) ->
"Security error. The connection is not secure."
else -> error.message
}
showError(message)
}
}
case .failure(let error):
let message: String
if error.message.lowercased().contains("timeout") {
message = "Connection timed out. Please try again."
} else if error.message.lowercased().contains("network") {
message = "Network error. Check your connection."
} else if error.message.lowercased().contains("ssl") {
message = "Security error. The connection is not secure."
} else {
message = error.message
}
showError(message: message)
Best Practices
When implementing HTTP/WebSocket transport:
- Use HTTPS: Never use plain HTTP for credential exchange
- Handle errors gracefully: Network requests can fail for many reasons
- Set appropriate timeouts: Balance user experience with reliability
- Validate server certificates: Ensure secure connections
- Clean up on completion: Close engagements when transfer completes