Skip to content

Commit c30e1d8

Browse files
committed
allowAuthorizationFallback flag in reissueDocument - update docs, readme
1 parent 4a55647 commit c30e1d8

131 files changed

Lines changed: 1736 additions & 1200 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,72 @@ openId4VciManager.issueDeferredDocument(deferredDocument) { result ->
963963
}
964964
```
965965

966+
#### Credential Re-Issuance
967+
968+
The library supports re-issuing previously issued credentials without requiring the user to go
969+
through the full authorization flow again. After a credential is successfully issued, the library
970+
automatically stores the authorization context. This metadata
971+
enables subsequent re-issuance using the stored refresh token.
972+
973+
##### Interactive Re-Issuance (User-Triggered)
974+
975+
Use this when the user explicitly triggers a credential refresh. If the stored tokens have expired, the library falls back to a full OAuth authorization flow:
976+
977+
```kotlin
978+
val openId4VciManager = wallet.createOpenId4VciManager()
979+
980+
openId4VciManager.reissueDocument(documentId) { event ->
981+
when (event) {
982+
is IssueEvent.Started -> {
983+
// Re-issuance process started
984+
}
985+
is IssueEvent.DocumentIssued -> {
986+
// New credential issued successfully
987+
// The old document is automatically deleted
988+
}
989+
is IssueEvent.DocumentDeferred -> {
990+
// Credential issuance is deferred (server-side processing)
991+
// Old document remains until the deferred credential is issued
992+
}
993+
is IssueEvent.Failure -> {
994+
// Re-issuance failed
995+
val cause = event.cause
996+
}
997+
is IssueEvent.Finished -> {
998+
// Re-issuance process completed
999+
}
1000+
else -> {}
1001+
}
1002+
}
1003+
```
1004+
1005+
##### Background Re-Issuance
1006+
1007+
For performing re-issuance in the background, pass `allowAuthorizationFallback = false` to `reissueDocument()` if you want
1008+
to prevent the library from triggering the authorization flow if the refresh token is no longer valid (default implementation is opening a browser). In this scenario a `ReissuanceAuthorizationException` is delivered via
1009+
`IssueEvent.Failure`:
1010+
1011+
```kotlin
1012+
openId4VciManager.reissueDocument(
1013+
documentId = documentId,
1014+
allowAuthorizationFallback = false,
1015+
) { event ->
1016+
when (event) {
1017+
is IssueEvent.Failure -> {
1018+
if (event.cause is ReissuanceAuthorizationException) {
1019+
// Tokens expired — ReIssuance failed
1020+
} else {
1021+
// Other error
1022+
}
1023+
}
1024+
is IssueEvent.DocumentIssued -> {
1025+
// Successfully re-issued in background
1026+
}
1027+
else -> {}
1028+
}
1029+
}
1030+
```
1031+
9661032
#### DPoP Support
9671033

9681034
DPoP (Demonstrating Proof-of-Possession) is a security mechanism that cryptographically binds OAuth

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
| [eu.europa.ec.eudi.wallet.dcapi](wallet-core/eu.europa.ec.eudi.wallet.dcapi/index.md) |
1111
| [eu.europa.ec.eudi.wallet.document](wallet-core/eu.europa.ec.eudi.wallet.document/index.md) |
1212
| [eu.europa.ec.eudi.wallet.issue.openid4vci](wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci/index.md) |
13+
| [eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci.dpop/index.md) |
14+
| [eu.europa.ec.eudi.wallet.issue.openid4vci.reissue](wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci.reissue/index.md) |
1315
| [eu.europa.ec.eudi.wallet.issue.openid4vci.transformations](wallet-core/eu.europa.ec.eudi.wallet.issue.openid4vci.transformations/index.md) |
1416
| [eu.europa.ec.eudi.wallet.logging](wallet-core/eu.europa.ec.eudi.wallet.logging/index.md) |
1517
| [eu.europa.ec.eudi.wallet.presentation](wallet-core/eu.europa.ec.eudi.wallet.presentation/index.md) |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Custom](index.md)/[Custom](-custom.md)
2+
3+
# Custom
4+
5+
[androidJvm]\
6+
constructor(secureArea: SecureArea, createKeySettingsBuilder: ([List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin-stdlib/kotlin.collections/-list/index.html)<Algorithm>) -> CreateKeySettings, keyUnlockDataProvider: [KeyUnlockDataProvider](../../-key-unlock-data-provider/index.md) = KeyUnlockDataProvider.None)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Custom](index.md)/[createKeySettingsBuilder](create-key-settings-builder.md)
2+
3+
# createKeySettingsBuilder
4+
5+
[androidJvm]\
6+
val [createKeySettingsBuilder](create-key-settings-builder.md): ([List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin-stdlib/kotlin.collections/-list/index.html)<Algorithm>) -> CreateKeySettings
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Custom](index.md)
2+
3+
# Custom
4+
5+
[androidJvm]\
6+
data class [Custom](index.md)(val secureArea: SecureArea, val createKeySettingsBuilder: ([List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin-stdlib/kotlin.collections/-list/index.html)<Algorithm>) -> CreateKeySettings, val keyUnlockDataProvider: [KeyUnlockDataProvider](../../-key-unlock-data-provider/index.md) = KeyUnlockDataProvider.None) : [DPopConfig](../index.md)
7+
8+
Custom DPoP configuration using a specific secure area.
9+
10+
This configuration allows full control over how DPoP keys are created and stored. The algorithms for key creation are automatically selected based on what both the authorization server and the secure area support.
11+
12+
## How It Works
13+
14+
1.
15+
The library checks the authorization server's supported DPoP algorithms
16+
2.
17+
It finds all algorithms supported by both server and [secureArea](secure-area.md)
18+
3.
19+
The list of matched algorithms is passed to [createKeySettingsBuilder](create-key-settings-builder.md)
20+
4.
21+
Your builder function selects an appropriate algorithm from the list
22+
5.
23+
A DPoP key is created using the returned settings
24+
6.
25+
The key is used to sign all DPoP proofs during issuance
26+
27+
## Example with Custom Secure Area
28+
29+
```kotlin
30+
val customConfig = DPopConfig.Custom(
31+
secureArea = myCloudSecureArea,
32+
createKeySettingsBuilder = { algorithms ->
33+
val algorithm = algorithms.first() // Select the first supported algorithm
34+
CloudKeySettings.Builder()
35+
.setAlgorithm(algorithm)
36+
.setKeyName("dpop_key")
37+
.setRequireUserAuth(true)
38+
.build()
39+
}
40+
)
41+
```
42+
43+
## Example with Android Keystore
44+
45+
```kotlin
46+
val androidConfig = DPopConfig.Custom(
47+
secureArea = AndroidKeystoreSecureArea.create(storage),
48+
createKeySettingsBuilder = { algorithms ->
49+
// Select the first signing algorithm from the list
50+
val algorithm = algorithms.firstOrNull { it.isSigning }
51+
?: throw IllegalStateException("No signing algorithm available")
52+
AndroidKeystoreCreateKeySettings.Builder(attestationChallenge)
53+
.setAlgorithm(algorithm) // e.g., ES256, ES384, ES512
54+
.setUserAuthenticationRequired(true, 30_000, setOf(
55+
UserAuthenticationType.BIOMETRIC
56+
))
57+
.setUseStrongBox(true)
58+
.build()
59+
}
60+
)
61+
```
62+
63+
## Constructors
64+
65+
| | |
66+
|---|---|
67+
| [Custom](-custom.md) | [androidJvm]<br>constructor(secureArea: SecureArea, createKeySettingsBuilder: ([List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin-stdlib/kotlin.collections/-list/index.html)&lt;Algorithm&gt;) -&gt; CreateKeySettings, keyUnlockDataProvider: [KeyUnlockDataProvider](../../-key-unlock-data-provider/index.md) = KeyUnlockDataProvider.None) |
68+
69+
## Properties
70+
71+
| Name | Summary |
72+
|---|---|
73+
| [createKeySettingsBuilder](create-key-settings-builder.md) | [androidJvm]<br>val [createKeySettingsBuilder](create-key-settings-builder.md): ([List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin-stdlib/kotlin.collections/-list/index.html)&lt;Algorithm&gt;) -&gt; CreateKeySettings<br>A function that creates CreateKeySettings from a list of supported algorithms. The algorithms parameter contains all algorithms that are supported by both the authorization server and the secure area. Your implementation should select an appropriate algorithm from this list and return valid settings for creating a key with that algorithm. |
74+
| [keyUnlockDataProvider](key-unlock-data-provider.md) | [androidJvm]<br>val [keyUnlockDataProvider](key-unlock-data-provider.md): [KeyUnlockDataProvider](../../-key-unlock-data-provider/index.md)<br>A [KeyUnlockDataProvider](../../-key-unlock-data-provider/index.md) that provides KeyUnlockData for unlocking the DPoP key when performing signing operations. This is invoked each time a DPoP proof needs to be signed. The provider receives the key alias and secure area as parameters and should return the appropriate unlock data, or null if no unlock is required. |
75+
| [secureArea](secure-area.md) | [androidJvm]<br>val [secureArea](secure-area.md): SecureArea<br>The secure area where DPoP keys are stored and managed. Must support at least one signing algorithm (e.g., ES256, ES384, ES512). |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Custom](index.md)/[keyUnlockDataProvider](key-unlock-data-provider.md)
2+
3+
# keyUnlockDataProvider
4+
5+
[androidJvm]\
6+
val [keyUnlockDataProvider](key-unlock-data-provider.md): [KeyUnlockDataProvider](../../-key-unlock-data-provider/index.md)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Custom](index.md)/[secureArea](secure-area.md)
2+
3+
# secureArea
4+
5+
[androidJvm]\
6+
val [secureArea](secure-area.md): SecureArea
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Default](index.md)
2+
3+
# Default
4+
5+
[androidJvm]\
6+
data object [Default](index.md) : [DPopConfig](../index.md)
7+
8+
Default DPoP configuration using Android Keystore.
9+
10+
This configuration automatically sets up DPoP with sensible defaults:
11+
12+
-
13+
Keys stored in Android Keystore
14+
-
15+
Storage in app's no-backup files directory
16+
-
17+
StrongBox support when available
18+
-
19+
No user authentication required for key usage
20+
-
21+
Algorithm automatically negotiated with the server
22+
23+
The actual configuration is created lazily when needed by calling make.
24+
25+
## Example
26+
27+
```kotlin
28+
val openId4VciConfig = OpenId4VciManager.Config.Builder()
29+
.withIssuerUrl("https://issuer.com")
30+
.withClientId("client-id")
31+
.withAuthFlowRedirectionURI("app://callback")
32+
.withDPopConfig(DPopConfig.Default)
33+
.build()
34+
```
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//[wallet-core](../../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../../index.md)/[DPopConfig](../index.md)/[Disabled](index.md)
2+
3+
# Disabled
4+
5+
[androidJvm]\
6+
data object [Disabled](index.md) : [DPopConfig](../index.md)
7+
8+
DPoP is disabled.
9+
10+
When this configuration is used, no DPoP proofs are generated or sent during credential issuance. Access tokens will not be bound to cryptographic keys.
11+
12+
Use this when:
13+
14+
-
15+
The authorization server does not support DPoP
16+
-
17+
DPoP is not required for your use case
18+
-
19+
Testing without DPoP protection
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//[wallet-core](../../../index.md)/[eu.europa.ec.eudi.wallet.issue.openid4vci.dpop](../index.md)/[DPopConfig](index.md)
2+
3+
# DPopConfig
4+
5+
sealed interface [DPopConfig](index.md)
6+
7+
Configuration for DPoP (Demonstrating Proof-of-Possession) in OpenID4VCI credential issuance.
8+
9+
DPoP is a security mechanism that binds OAuth 2.0 access tokens to cryptographic keys, preventing token theft and replay attacks. This sealed interface defines the available DPoP configuration options for credential issuance flows.
10+
11+
## Available Configurations
12+
13+
-
14+
[Disabled](-disabled/index.md) - DPoP is not used
15+
-
16+
[Default](-default/index.md) - Uses Android Keystore with default settings
17+
-
18+
[Custom](-custom/index.md) - Uses a custom secure area with custom key settings
19+
20+
## Example Usage
21+
22+
```kotlin
23+
// Disable DPoP
24+
val config = DPopConfig.Disabled
25+
26+
// Use default configuration
27+
val config = DPopConfig.Default
28+
29+
// Use custom secure area
30+
val config = DPopConfig.Custom(
31+
secureArea = mySecureArea,
32+
createKeySettingsBuilder = { algorithms ->
33+
MyCreateKeySettings(algorithms.first())
34+
}
35+
)
36+
```
37+
38+
#### See also
39+
40+
| |
41+
|---|
42+
| [DPopConfig.Disabled](-disabled/index.md) |
43+
| [DPopConfig.Default](-default/index.md) |
44+
| [DPopConfig.Custom](-custom/index.md) |
45+
46+
#### Inheritors
47+
48+
| |
49+
|---|
50+
| [Disabled](-disabled/index.md) |
51+
| [Default](-default/index.md) |
52+
| [Custom](-custom/index.md) |
53+
54+
## Types
55+
56+
| Name | Summary |
57+
|---|---|
58+
| [Custom](-custom/index.md) | [androidJvm]<br>data class [Custom](-custom/index.md)(val secureArea: SecureArea, val createKeySettingsBuilder: ([List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin-stdlib/kotlin.collections/-list/index.html)&lt;Algorithm&gt;) -&gt; CreateKeySettings, val keyUnlockDataProvider: [KeyUnlockDataProvider](../-key-unlock-data-provider/index.md) = KeyUnlockDataProvider.None) : [DPopConfig](index.md)<br>Custom DPoP configuration using a specific secure area. |
59+
| [Default](-default/index.md) | [androidJvm]<br>data object [Default](-default/index.md) : [DPopConfig](index.md)<br>Default DPoP configuration using Android Keystore. |
60+
| [Disabled](-disabled/index.md) | [androidJvm]<br>data object [Disabled](-disabled/index.md) : [DPopConfig](index.md)<br>DPoP is disabled. |

0 commit comments

Comments
 (0)