Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ The Android app uses intent filters to handle deep links. Configure three types
</intent-filter>
```

**4. Custom Tabs Package Visibility (Required for OAuth)**

The OAuth authorization step opens the issuer's login page in a Chrome Custom Tab. On
Android 11 (API 30) and above, [package visibility](https://developer.android.com/training/package-visibility)
rules prevent the app from discovering a Custom Tabs provider unless it is declared. Add the
following `<queries>` element as a direct child of the `<manifest>` element (a sibling of
`<application>`), otherwise the OAuth flow will fail to launch a browser:

```xml
<!-- TODO: add custom tab query permission -->
<!-- Required on Android 11+ for Multipaz's OAuth flow to discover a Chrome Custom Tabs
provider via CustomTabsClient.getPackageName() -->
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
```

#### **Step 2: Handle URLs in MainActivity**

In `MainActivity.kt`, the app handles incoming URLs:
Expand Down Expand Up @@ -461,7 +480,7 @@ val text = when (provisioningState) {
ProvisioningModel.ProcessingAuthorization -> "Processing authorization..."
ProvisioningModel.Authorized -> "Authorized"
ProvisioningModel.RequestingCredentials -> "Requesting credentials..."
ProvisioningModel.CredentialsIssued -> "Credentials issued"
is ProvisioningModel.CredentialsIssued -> "Credentials issued"
is ProvisioningModel.Error -> throw IllegalStateException()
is ProvisioningModel.Authorizing -> throw IllegalStateException()
}
Expand Down Expand Up @@ -528,7 +547,7 @@ LaunchedEffect(Unit) {

LaunchedEffect(provisioningModel) {
provisioningModel.state.collect { state ->
if (state == ProvisioningModel.CredentialsIssued) {
if (state is ProvisioningModel.CredentialsIssued) {
registrationManager.refresh("credentials issued")
}
}
Expand Down
3 changes: 1 addition & 2 deletions codelabs/Utopia Wholesale Codelab/Holder/1-Storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,13 @@ single<DocumentStore> {

### **Step 5: Configure `DocumentTypeRepository` in Koin**

The `DocumentTypeRepository` registers supported document types that your wallet can handle. For this codelab, we'll register `DrivingLicense` and `Loyalty` document types:
The `DocumentTypeRepository` registers supported document types that your wallet can handle. For this codelab, we'll register the `DrivingLicense` document type:

```kotlin
//TODO: define DocumentTypeRepository in Koin module
single<DocumentTypeRepository> {
DocumentTypeRepository().apply {
addDocumentType(DrivingLicense.getDocumentType())
addDocumentType(Loyalty.getDocumentType())
}
}
```
Expand Down
16 changes: 8 additions & 8 deletions codelabs/Utopia Wholesale Codelab/Holder/2-Holder.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ single<PresentmentSource> {
documentTypeRepository = get(),
preferSignatureToKeyAgreement = true,
// Match domains used when storing credentials via OpenID4VCI
domainMdocSignature = TestAppUtils.CREDENTIAL_DOMAIN_MDOC_USER_AUTH,
domainMdocKeyAgreement = TestAppUtils.CREDENTIAL_DOMAIN_MDOC_MAC_USER_AUTH,
domainKeylessSdJwt = TestAppUtils.CREDENTIAL_DOMAIN_SDJWT_KEYLESS,
domainKeyBoundSdJwt = TestAppUtils.CREDENTIAL_DOMAIN_SDJWT_USER_AUTH,
domainsMdocSignature = listOf(TestAppUtils.CREDENTIAL_DOMAIN_MDOC_USER_AUTH),
domainsMdocKeyAgreement = listOf(TestAppUtils.CREDENTIAL_DOMAIN_MDOC_MAC_USER_AUTH),
domainsKeylessSdJwt = listOf(TestAppUtils.CREDENTIAL_DOMAIN_SDJWT_KEYLESS),
domainsKeyBoundSdJwt = listOf(TestAppUtils.CREDENTIAL_DOMAIN_SDJWT_USER_AUTH),
)
}
```
Expand All @@ -208,10 +208,10 @@ single<PresentmentSource> {
* `documentTypeRepository` - For managing document types
* `preferSignatureToKeyAgreement = true` prioritizes signature-based authentication over key agreement.
* Domain configurations match those used during credential storage via OpenID4VCI to ensure proper credential binding:
* `domainMdocSignature` - Domain for mDoc signature-based credentials
* `domainMdocKeyAgreement` - Domain for mDoc MAC/key agreement credentials
* `domainKeylessSdJwt` - Domain for keyless SD-JWT credentials
* `domainKeyBoundSdJwt` - Domain for key-bound SD-JWT credentials
* `domainsMdocSignature` - List of domains for mDoc signature-based credentials
* `domainsMdocKeyAgreement` - List of domains for mDoc MAC/key agreement credentials
* `domainsKeylessSdJwt` - List of domains for keyless SD-JWT credentials
* `domainsKeyBoundSdJwt` - List of domains for key-bound SD-JWT credentials

The `PresentmentModel` (which manages the presentation lifecycle and state transitions like `IDLE`, `CONNECTING`, `COMPLETED`, etc.) is also configured in the Koin module and can be injected wherever needed in your app.

Expand Down