diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs
index 5098f2b9..980c59ce 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs
@@ -30,8 +30,10 @@ public interface IOid4VciClientService
/// The issuers uri
/// The client options
/// Optional language tag
+ /// Specifies whether Sd-Jwt or MDoc should be issued
+ /// Optional language tag
///
- Task InitiateAuthFlow(Uri uri, ClientOptions clientOptions, Option language);
+ Task InitiateAuthFlow(Uri uri, ClientOptions clientOptions, Option language, Option> credentialType, Option specVersion);
///
/// Requests a verifiable credential using the authorization code flow.
@@ -40,18 +42,17 @@ public interface IOid4VciClientService
///
/// A list of credentials.
///
- Task> RequestCredentialSet(IssuanceSession issuanceSession);
+ Task>> RequestCredentialSet(IssuanceSession issuanceSession);
///
/// Requests a verifiable credential using the authorization code flow and C''.
///
/// Holds authorization session relevant information.
/// The AuthorizationRequest that is associated witht the ad-hoc crednetial issuance
- /// Specifies whether Sd-Jwt or MDoc should be issued
///
/// A list of credentials.
///
- Task> RequestOnDemandCredentialSet(IssuanceSession issuanceSession, AuthorizationRequest authorizationRequest, OneOf credentialType);
+ Task>> RequestOnDemandCredentialSet(IssuanceSession issuanceSession, AuthorizationRequest authorizationRequest);
///
/// Processes a credential offer
@@ -66,7 +67,7 @@ public interface IOid4VciClientService
/// ///
/// Credential offer and Issuer Metadata
/// The Transaction Code.
- Task> AcceptOffer(
+ Task>> AcceptOffer(
CredentialOfferMetadata credentialOfferMetadata,
string? transactionCode);
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs
index 37126b65..be79f0db 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs
@@ -1,4 +1,5 @@
using Hyperledger.Aries.Agents;
+using LanguageExt;
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models;
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records;
@@ -44,10 +45,12 @@ public interface IAuthFlowSessionStorage
/// flow.
///
/// Session State Identifier of a Authorization Code Flow session
+ /// Session State Identifier of a Authorization Code Flow session
///
Task StoreAsync(
IAgentContext agentContext,
AuthorizationData authorizationData,
AuthorizationCodeParameters authorizationCodeParameters,
- AuthFlowSessionState authFlowSessionState);
+ AuthFlowSessionState authFlowSessionState,
+ Option specVersion);
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs
index 0f0862e4..2317bfff 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs
@@ -1,5 +1,6 @@
using Hyperledger.Aries.Agents;
using Hyperledger.Aries.Storage;
+using LanguageExt;
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Abstractions;
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models;
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records;
@@ -22,16 +23,17 @@ public AuthFlowSessionStorage(IWalletRecordService recordService)
private readonly IWalletRecordService _recordService;
///
- public async Task StoreAsync(
- IAgentContext agentContext,
+ public async Task StoreAsync(IAgentContext agentContext,
AuthorizationData authorizationData,
AuthorizationCodeParameters authorizationCodeParameters,
- AuthFlowSessionState authFlowSessionState)
+ AuthFlowSessionState authFlowSessionState,
+ Option specVersion)
{
var record = new AuthFlowSessionRecord(
authorizationData,
authorizationCodeParameters,
- authFlowSessionState);
+ authFlowSessionState,
+ specVersion.ToNullable());
await _recordService.AddAsync(agentContext.Wallet, record);
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs
index a38aebf3..a18aac26 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs
@@ -36,6 +36,9 @@ public record AuthorizationDetails
[JsonProperty("locations", NullValueHandling = NullValueHandling.Ignore)]
public string[]? Locations { get; }
+
+ [JsonProperty("credential_identifiers", NullValueHandling = NullValueHandling.Ignore)]
+ public string[]? CredentialIdentifiers { get; }
internal AuthorizationDetails(
string credentialConfigurationId,
@@ -44,4 +47,21 @@ internal AuthorizationDetails(
CredentialConfigurationId = credentialConfigurationId;
Locations = locations;
}
+
+ [JsonConstructor]
+ private AuthorizationDetails(
+ string format,
+ string? vct,
+ string? docType,
+ string? credentialConfigurationId,
+ string[]? locations,
+ string[]? credentialIdentifiers)
+ {
+ Format = format;
+ Vct = vct;
+ DocType = docType;
+ CredentialConfigurationId = credentialConfigurationId;
+ Locations = locations;
+ CredentialIdentifiers = credentialIdentifiers;
+ }
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/CredentialIdentifier.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/CredentialIdentifier.cs
new file mode 100644
index 00000000..4b45839a
--- /dev/null
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/CredentialIdentifier.cs
@@ -0,0 +1,10 @@
+namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models;
+
+public struct CredentialIdentifier
+{
+ private string Value { get; }
+
+ public override string ToString() => Value;
+
+ public CredentialIdentifier(string id) => Value = id;
+}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs
index a1f30722..576fffc2 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs
@@ -37,6 +37,11 @@ public AuthFlowSessionState AuthFlowSessionState
/// The parameters for the 'authorization_code' grant type.
///
public AuthorizationCodeParameters AuthorizationCodeParameters { get; }
+
+ ///
+ /// Used to track the VCI Specification verison
+ ///
+ public int? SpecVersion { get; }
///
/// Initializes a new instance of the class.
@@ -58,15 +63,18 @@ public AuthFlowSessionRecord()
///
///
///
+ ///
public AuthFlowSessionRecord(
AuthorizationData authorizationData,
AuthorizationCodeParameters authorizationCodeParameters,
- AuthFlowSessionState authFlowSessionState)
+ AuthFlowSessionState authFlowSessionState,
+ int? specVersion)
{
AuthFlowSessionState = authFlowSessionState;
RecordVersion = 1;
AuthorizationCodeParameters = authorizationCodeParameters;
AuthorizationData = authorizationData;
+ SpecVersion = specVersion;
}
}
@@ -94,6 +102,7 @@ public static class AuthFlowSessionRecordFun
{
private const string AuthorizationDataJsonKey = "authorization_data";
private const string AuthorizationCodeParametersJsonKey = "authorization_code_parameters";
+ private const string SpecVersionJsonKey = "spec_version";
public static JObject EncodeToJson(this AuthFlowSessionRecord record)
{
@@ -104,7 +113,8 @@ public static JObject EncodeToJson(this AuthFlowSessionRecord record)
{
{ nameof(RecordBase.Id), record.Id },
{ AuthorizationDataJsonKey, authorizationData },
- { AuthorizationCodeParametersJsonKey, authorizationCodeParameters }
+ { AuthorizationCodeParametersJsonKey, authorizationCodeParameters },
+ { SpecVersionJsonKey, record.SpecVersion }
};
}
@@ -119,8 +129,10 @@ public static AuthFlowSessionRecord DecodeFromJson(JObject json)
var authorizationData = AuthorizationDataFun
.DecodeFromJson(json[AuthorizationDataJsonKey]!.ToObject()!);
-
- var result = new AuthFlowSessionRecord(authorizationData, authCodeParameters!, id);
+
+ var specVersion = json[SpecVersionJsonKey]!.ToObject();
+
+ var result = new AuthFlowSessionRecord(authorizationData, authCodeParameters!, id, specVersion);
return result;
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs
index ad912627..a50c21bc 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs
@@ -69,6 +69,6 @@ public record OAuthToken
///
/// Gets or sets the credential identifier.
///
- [JsonProperty("credential_identifiers")]
- public AuthorizationDetails? CredentialIdentifier { get; set; }
+ [JsonProperty("authorization_details", NullValueHandling = NullValueHandling.Ignore)]
+ public AuthorizationDetails[]? AuthorizationDetails { get; set; }
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/ScopeIsNullOrWhitespaceError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/ScopeIsNullOrWhitespaceError.cs
new file mode 100644
index 00000000..54fdd53b
--- /dev/null
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/ScopeIsNullOrWhitespaceError.cs
@@ -0,0 +1,6 @@
+using WalletFramework.Core.Functional;
+
+namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors;
+
+public record ScopeIsNullOrWhitespaceError()
+ : Error("The scope is null or whitespace");
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/ScopedCredentialConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/ScopedCredentialConfiguration.cs
new file mode 100644
index 00000000..e825e5ff
--- /dev/null
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/ScopedCredentialConfiguration.cs
@@ -0,0 +1,31 @@
+using LanguageExt;
+using Newtonsoft.Json.Linq;
+using WalletFramework.Core.Functional;
+using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models;
+
+namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models;
+
+public record ScopedCredentialConfiguration(CredentialConfigurationId CredentialConfigurationId, Option Scope);
+
+public static class ScopedCredentialConfigurationExtensions
+{
+ private const string ScopeJsonKey = "scope";
+ private const string CredentialConfigurationIdJsonKey = "credential_configuration_id";
+
+ public static JObject EncodeToJson(this ScopedCredentialConfiguration scopedCredentialConfiguration)
+ {
+ return new JObject
+ {
+ { ScopeJsonKey, scopedCredentialConfiguration.Scope.MatchUnsafe(scope => scope.ToString(), () => null) },
+ { CredentialConfigurationIdJsonKey, scopedCredentialConfiguration.CredentialConfigurationId.ToString() }
+ };
+ }
+
+ public static ScopedCredentialConfiguration DecodeFromJson(JObject json)
+ {
+ var scope = Scope.OptionalScope(json[ScopeJsonKey]!);
+ var credentialConfigurationId = CredentialConfigurationId.ValidCredentialConfigurationId(json[CredentialConfigurationIdJsonKey]!.ToString()).UnwrapOrThrow();
+
+ return new ScopedCredentialConfiguration(credentialConfigurationId, scope);
+ }
+}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs
index b159396c..cc9ec950 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs
@@ -4,8 +4,8 @@
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models;
using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models;
using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models;
-using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc;
-using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt;
+using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models;
+using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models;
using WalletFramework.Oid4Vc.Oid4Vci.CredResponse;
using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models;
using WalletFramework.Oid4Vc.Oid4Vp.Models;
@@ -14,10 +14,11 @@ namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Abstractions;
public interface ICredentialRequestService
{
- public Task> RequestCredentials(
- OneOf configuration,
+ public Task>> RequestCredentials(
+ KeyValuePair configurationPair,
IssuerMetadata issuerMetadata,
OneOf token,
Option clientOptions,
- Option authorizationRequest);
+ Option authorizationRequest,
+ Option specVersion);
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs
index cdd912d4..5b3c5f3b 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs
@@ -12,8 +12,7 @@
using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models;
using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models;
using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models;
-using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc;
-using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt;
+using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models;
using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Abstractions;
using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models;
using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models.Mdoc;
@@ -50,7 +49,9 @@ public CredentialRequestService(
private async Task CreateCredentialRequest(
KeyId keyId,
Format format,
+ OneOf credentialIdentification,
OneOf token,
+ Option specVersion,
IssuerMetadata issuerMetadata,
Option clientOptions,
Option authorizationRequest)
@@ -90,8 +91,8 @@ await batchCredentialIssuance.BatchSize.Match(
None: async () =>
proof = await GetProofOfPossessionAsync(keyId, issuerMetadata, cNonce, clientOptions));
});
-
- return new CredentialRequest(format, proof, proofs, sessionTranscript);
+
+ return new CredentialRequest(credentialIdentification, format, specVersion, proof, proofs, sessionTranscript);
}
private async Task GetProofOfPossessionAsync(KeyId keyId, IssuerMetadata issuerMetadata, string cNonce, Option clientOptions)
@@ -132,74 +133,92 @@ private async Task GenerateKbProofOfPossession(
Option.None);
}
- async Task> ICredentialRequestService.RequestCredentials(
- OneOf configuration,
+ async Task>> ICredentialRequestService.RequestCredentials(
+ KeyValuePair configurationPair,
IssuerMetadata issuerMetadata,
OneOf token,
Option clientOptions,
- Option authorizationRequest)
+ Option authorizationRequest,
+ Option specVersion)
{
- var keyId = await _keyStore.GenerateKey(isPermanent: authorizationRequest.IsNone);
-
- var requestJson = await configuration.Match(
- async sdJwt =>
- {
- var vciRequest = await CreateCredentialRequest(
- keyId,
- sdJwt.Format,
- token,
- issuerMetadata,
- clientOptions,
- authorizationRequest);
-
- var result = new SdJwtCredentialRequest(vciRequest, sdJwt.Vct);
- return result.EncodeToJson();
- },
- async mdoc =>
- {
- var vciRequest = await CreateCredentialRequest(
- keyId,
- mdoc.Format,
- token,
- issuerMetadata,
- clientOptions,
- authorizationRequest);
-
- var result = new MdocCredentialRequest(vciRequest, mdoc);
- return result.EncodeToJson();
- }
- );
-
- var content = new StringContent(
- requestJson,
- Encoding.UTF8,
- "application/json");
+ var credentialIdentifications =
+ token.Match(
+ oauthToken => oauthToken.AuthorizationDetails?.First().CredentialIdentifiers,
+ dPopToken => dPopToken.Token.AuthorizationDetails?.First().CredentialIdentifiers)?
+ .Select(identifier => (OneOf) new CredentialIdentifier(identifier))
+ ?? new List>() { configurationPair.Key };
+
+ var responses = new List>();
+ foreach (var credentialIdentification in credentialIdentifications)
+ {
+ var keyId = await _keyStore.GenerateKey(isPermanent: authorizationRequest.IsNone);
- var response = await token.Match(
- async authToken => await _httpClient
- .WithAuthorizationHeader(authToken)
- .PostAsync(issuerMetadata.CredentialEndpoint, content),
- async dPopToken =>
- {
- var config = dPopToken.DPop.Config with
+ var requestJson = await configurationPair.Value.Match(
+ async sdJwt =>
{
- Audience = issuerMetadata.CredentialEndpoint.ToStringWithoutTrail(),
- OAuthToken = dPopToken.Token
- };
-
- var dPopResponse = await _dPopHttpClient.Post(
- issuerMetadata.CredentialEndpoint,
- config,
- () => content);
-
- return dPopResponse.ResponseMessage;
- });
-
- var responseContent = await response.Content.ReadAsStringAsync();
+ var vciRequest = await CreateCredentialRequest(
+ keyId,
+ sdJwt.Format,
+ credentialIdentification,
+ token,
+ specVersion,
+ issuerMetadata,
+ clientOptions,
+ authorizationRequest);
+
+ var result = new SdJwtCredentialRequest(vciRequest, sdJwt.Vct);
+ return result.EncodeToJson();
+ },
+ async mdoc =>
+ {
+ var vciRequest = await CreateCredentialRequest(
+ keyId,
+ mdoc.Format,
+ credentialIdentification,
+ token,
+ specVersion,
+ issuerMetadata,
+ clientOptions,
+ authorizationRequest);
+
+ var result = new MdocCredentialRequest(vciRequest, mdoc);
+ return result.EncodeToJson();
+ }
+ );
+
+ var content = new StringContent(
+ requestJson,
+ Encoding.UTF8,
+ "application/json");
+
+ var response = await token.Match(
+ async authToken => await _httpClient
+ .WithAuthorizationHeader(authToken)
+ .PostAsync(issuerMetadata.CredentialEndpoint, content),
+ async dPopToken =>
+ {
+ var config = dPopToken.DPop.Config with
+ {
+ Audience = issuerMetadata.CredentialEndpoint.ToStringWithoutTrail(),
+ OAuthToken = dPopToken.Token
+ };
+
+ var dPopResponse = await _dPopHttpClient.Post(
+ issuerMetadata.CredentialEndpoint,
+ config,
+ () => content);
+
+ return dPopResponse.ResponseMessage;
+ });
+
+ var credentialResponse =
+ from jObject in JsonFun.ParseAsJObject(await response.Content.ReadAsStringAsync())
+ from credResponse in CredentialResponse.ValidCredentialResponse(jObject, keyId)
+ select credResponse;
+
+ responses.Add(credentialResponse);
+ }
- return
- from jObject in JsonFun.ParseAsJObject(responseContent)
- from credResponse in CredentialResponse.ValidCredentialResponse(jObject, keyId)
- select credResponse;
+ return responses.TraverseAll(item => item);
}
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs
index 55702448..8234754f 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs
@@ -1,8 +1,11 @@
using LanguageExt;
using Newtonsoft.Json.Linq;
+using OneOf;
using WalletFramework.Core.Base64Url;
using WalletFramework.MdocLib.Security;
+using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models;
using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models;
+using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models;
namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models;
@@ -11,25 +14,13 @@ namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models;
/// This request contains the format of the credential, the type of credential,
/// and a proof of possession of the key material the issued credential shall be bound to.
///
-public record CredentialRequest(Format Format, Option Proof, Option Proofs, Option SessionTranscript)
-{
- ///
- /// Gets the proof of possession of the key material the issued credential shall be bound to.
- ///
- public Option Proof { get; } = Proof;
-
- ///
- /// Gets one or more proof of possessions of the key material the issued credential shall be bound to.
- ///
- public Option Proofs { get; } = Proofs;
-
- ///
- /// Gets the format of the credential to be issued.
- ///
- public Format Format { get; } = Format;
-
- public Option SessionTranscript { get; } = SessionTranscript;
-}
+public record CredentialRequest(
+ OneOf CredentialIdentification,
+ Format Format,
+ Option SpecVersion,
+ Option Proof,
+ Option Proofs,
+ Option SessionTranscript);
public static class CredentialRequestFun
{
@@ -37,6 +28,8 @@ public static class CredentialRequestFun
private const string ProofsJsonKey = "proofs";
private const string FormatJsonKey = "format";
private const string SessionTranscriptKey = "session_transcript";
+ private const string CredentialIdentifierKey = "credential_identifier";
+ private const string CredentialConfigurationIdKey = "credential_configuration_id";
public static JObject EncodeToJson(this CredentialRequest request)
{
@@ -57,8 +50,40 @@ public static JObject EncodeToJson(this CredentialRequest request)
result.Add(SessionTranscriptKey, Base64UrlString.CreateBase64UrlString(sessionTranscript.ToCbor().ToJSONBytes()).ToString());
});
- result.Add(FormatJsonKey, request.Format.ToString());
-
+ request.CredentialIdentification.Match(
+ identifier =>
+ {
+ result.Add(CredentialIdentifierKey, identifier.ToString());
+ return Unit.Default;
+ },
+ configurationId =>
+ {
+ request.SpecVersion.Match(specVersion =>
+ {
+ switch (specVersion)
+ {
+ case 14:
+ result.Add(FormatJsonKey, request.Format.ToString());
+ break;
+ case 15:
+ result.Add(CredentialConfigurationIdKey, configurationId.ToString());
+ break;
+ default:
+ result.Add(FormatJsonKey, request.Format.ToString());
+ result.Add(CredentialConfigurationIdKey, configurationId.ToString());
+ break;
+ }
+ },
+ () =>
+ {
+ result.Add(FormatJsonKey, request.Format.ToString());
+ result.Add(CredentialConfigurationIdKey, configurationId.ToString());
+ }
+ );
+
+ return Unit.Default;
+ });
+
return result;
}
}
diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs
index cd0235ff..02c41a84 100644
--- a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs
+++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs
@@ -149,7 +149,7 @@ from issState in code.IssuerState
var authServerMetadata = await FetchAuthorizationServerMetadataAsync(issuerMetadata, offer.CredentialOffer);
var authorizationRequestUri = authServerMetadata.PushedAuthorizationRequestEndpoint.IsNullOrEmpty()
- ? new Uri(authServerMetadata.AuthorizationEndpoint + "?" + vciAuthorizationRequest.ToQueryString())
+ ? new Uri(authServerMetadata.AuthorizationEndpoint + vciAuthorizationRequest.ToQueryString())
: await GetRequestUriUsingPushedAuthorizationRequest(authServerMetadata, vciAuthorizationRequest);
var authorizationData = new AuthorizationData(
@@ -164,12 +164,13 @@ await _authFlowSessionStorage.StoreAsync(
context,
authorizationData,
authorizationCodeParameters,
- sessionId);
+ sessionId,
+ Option.None);
return authorizationRequestUri;
}
- public async Task InitiateAuthFlow(Uri uri, ClientOptions clientOptions, Option language)
+ public async Task InitiateAuthFlow(Uri uri, ClientOptions clientOptions, Option language, Option> credentialType, Option specVersion)
{
var locale = language.Match(
some => some,
@@ -186,32 +187,43 @@ public async Task InitiateAuthFlow(Uri uri, ClientOptions clientOptions, Op
var sessionId = AuthFlowSessionState.CreateAuthFlowSessionState();
var authorizationCodeParameters = CreateAndStoreCodeChallenge();
- var scope = validIssuerMetadata.CredentialConfigurationsSupported.First().Value.Match(
- sdJwtConfig => sdJwtConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()),
- mdDocConfig => mdDocConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString())
- );
+ var relevantConfigurations = validIssuerMetadata.CredentialConfigurationsSupported
+ .Where(config =>
+ {
+ return credentialType.Match(
+ type => config.Value.Match(
+ sdJwtConfig => type.IsT0 && sdJwtConfig.Vct == type.AsT0,
+ mDocConfig => type.IsT1 && mDocConfig.DocType == type.AsT1),
+ () => true);
+ }).ToList();
- var authorizationDetails = validIssuerMetadata.CredentialConfigurationsSupported.First().Value.Match(
- sdJwtConfig => new AuthorizationDetails(
- validIssuerMetadata.CredentialConfigurationsSupported.First().Key.ToString(),
- validIssuerMetadata.AuthorizationServers.ToNullable()?.Select(id => id.ToString()).ToArray()),
- mdDocConfig => new AuthorizationDetails(
- validIssuerMetadata.CredentialConfigurationsSupported.First().Key.ToString(),
- validIssuerMetadata.AuthorizationServers.ToNullable()?.Select(id => id.ToString()).ToArray())
- );
+ var scopes = relevantConfigurations
+ .Select(config => config.Value.Match(
+ sdJwtConfig => sdJwtConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()),
+ mdDocConfig => mdDocConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString())
+ ))
+ .Where(option => option.IsSome)
+ .Select(option => option.Fallback(string.Empty))
+ .Distinct();
+
+ var authorizationDetails = relevantConfigurations
+ .Select(config => new AuthorizationDetails(
+ config.Key.ToString(),
+ validIssuerMetadata.AuthorizationServers.ToNullable()?.Select(id => id.ToString()).ToArray()
+ )).ToArray();
var vciAuthorizationRequest = new VciAuthorizationRequest(
sessionId,
clientOptions,
authorizationCodeParameters,
- [authorizationDetails],
- scope.ToNullable(),
+ authorizationDetails,
+ string.Join(" ", scopes),
null,
null,
null);
var authorizationRequestUri = authServerMetadata.PushedAuthorizationRequestEndpoint.IsNullOrEmpty()
- ? new Uri(authServerMetadata.AuthorizationEndpoint + "?" + vciAuthorizationRequest.ToQueryString())
+ ? new Uri(authServerMetadata.AuthorizationEndpoint + vciAuthorizationRequest.ToQueryString())
: await GetRequestUriUsingPushedAuthorizationRequest(authServerMetadata, vciAuthorizationRequest);
//TODO: Select multiple configurationIds
@@ -219,15 +231,18 @@ public async Task InitiateAuthFlow(Uri uri, ClientOptions clientOptions, Op
clientOptions,
validIssuerMetadata,
authServerMetadata,
- Option.None,
- validIssuerMetadata.CredentialConfigurationsSupported.Keys.ToList());
+ Option.None,
+ relevantConfigurations
+ .Select(config => config.Key)
+ .ToList());
var context = await _agentProvider.GetContextAsync();
await _authFlowSessionStorage.StoreAsync(
context,
authorizationData,
authorizationCodeParameters,
- sessionId);
+ sessionId,
+ specVersion);
return authorizationRequestUri;
},
@@ -251,12 +266,10 @@ private async Task GetRequestUriUsingPushedAuthorizationRequest(Authorizati
+ "&request_uri=" + System.Net.WebUtility.UrlEncode(parResponse.RequestUri.ToString()));
}
- public async Task> AcceptOffer(CredentialOfferMetadata credentialOfferMetadata, string? transactionCode)
+ public async Task>> AcceptOffer(CredentialOfferMetadata credentialOfferMetadata, string? transactionCode)
{
var issuerMetadata = credentialOfferMetadata.IssuerMetadata;
- // TODO: Support multiple configs
- var configId = credentialOfferMetadata.CredentialOffer.CredentialConfigurationIds.First();
- var configuration = issuerMetadata.CredentialConfigurationsSupported[configId];
+
var preAuthorizedCode =
from grants in credentialOfferMetadata.CredentialOffer.Grants
from preAuthCode in grants.PreAuthorizedCode
@@ -276,63 +289,72 @@ from preAuthCode in grants.PreAuthorizedCode
authorizationServerMetadata,
issuerMetadata.CredentialNonceEndpoint);
- var validResponse = await _credentialRequestService.RequestCredentials(
- configuration,
+ // TODO: Support multiple configs
+ var configurationId = credentialOfferMetadata.CredentialOffer.CredentialConfigurationIds.First();
+ var configurationPair = issuerMetadata.CredentialConfigurationsSupported.Single(config => config.Key == configurationId);
+
+ var validResponses = await _credentialRequestService.RequestCredentials(
+ configurationPair,
issuerMetadata,
token,
Option.None,
- Option.None);
-
- var credentialSet = new CredentialSetRecord();
-
+ Option.None,
+ Option.None);
+
+ var credentialSets = new List();
var result =
- from response in validResponse
- let credentialsOrTransactionId = response.CredentialsOrTransactionId
- select credentialsOrTransactionId.Match(
- async creds =>
- {
- foreach (var credential in creds)
+ from responses in validResponses
+ let credentialSet = new CredentialSetRecord()
+ select
+ from response in responses
+ let credentialsOrTransactionId = response.CredentialsOrTransactionId
+ select credentialsOrTransactionId.Match(
+ async creds =>
{
- await credential.Value.Match(
- async sdJwt =>
- {
- var record = sdJwt.Decoded.ToRecord(
- configuration.AsT0,
- response.KeyId,
- credentialSet.CredentialSetId,
- creds.Count > 1);
-
- var context = await _agentProvider.GetContextAsync();
- await _sdJwtService.AddAsync(context, record);
+ foreach (var credential in creds)
+ {
+ await credential.Value.Match(
+ async sdJwt =>
+ {
+ var record = sdJwt.Decoded.ToRecord(
+ configurationPair.Value.AsT0,
+ response.KeyId,
+ credentialSet.CredentialSetId,
+ creds.Count > 1);
+
+ var context = await _agentProvider.GetContextAsync();
+ await _sdJwtService.AddAsync(context, record);
- credentialSet.AddSdJwtData(record);
- },
- async mdoc =>
- {
- var displays = MdocFun.CreateMdocDisplays(configuration.AsT1);
-
- var record = mdoc.Decoded.ToRecord(
- displays,
- response.KeyId,
- credentialSet.CredentialSetId,
- creds.Count > 1);
-
- await _mdocStorage.Add(record);
+ credentialSet.AddSdJwtData(record);
+ },
+ async mdoc =>
+ {
+ var displays = MdocFun.CreateMdocDisplays(configurationPair.Value.AsT1);
+
+ var record = mdoc.Decoded.ToRecord(
+ displays,
+ response.KeyId,
+ credentialSet.CredentialSetId,
+ creds.Count > 1);
+
+ await _mdocStorage.Add(record);
- credentialSet.AddMDocData(record, issuerMetadata.CredentialIssuer);
- });
- }
- },
- // ReSharper disable once UnusedParameter.Local
- transactionId => throw new NotImplementedException());
+ credentialSet.AddMDocData(record, issuerMetadata.CredentialIssuer);
+ });
+ }
+ credentialSets.Add(credentialSet);
+ },
+ // ReSharper disable once UnusedParameter.Local
+ transactionId => throw new NotImplementedException());
- await result.OnSuccess(async task =>
+ await result.OnSuccess(async tasks => await Task.WhenAll(tasks));
+
+ foreach (var credentialSet in credentialSets)
{
- await task;
await _credentialSetStorage.Add(credentialSet);
- });
-
- return credentialSet;
+ }
+
+ return credentialSets;
}
public async Task> ProcessOffer(Uri credentialOffer, Option language)
@@ -350,25 +372,24 @@ from metadata in _issuerMetadataService.ProcessMetadata(offer.CredentialIssuer,
}
///
- public async Task> RequestCredentialSet(IssuanceSession issuanceSession)
+ public async Task>> RequestCredentialSet(IssuanceSession issuanceSession)
{
var context = await _agentProvider.GetContextAsync();
var session = await _authFlowSessionStorage.GetAsync(context, issuanceSession.AuthFlowSessionState);
- var credConfiguration = session
+ var relevantConfigurations = session
.AuthorizationData
.IssuerMetadata
.CredentialConfigurationsSupported
- .Where(config => session.AuthorizationData.CredentialConfigurationIds.Contains(config.Key))
- .Select(pair => pair.Value);
-
- var scope = session
- .AuthorizationData
- .IssuerMetadata
- .CredentialConfigurationsSupported.First().Value.Match(
- sdJwtConfig => sdJwtConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()),
- mdDocConfig => mdDocConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()));
+ .Where(config => session.AuthorizationData.CredentialConfigurationIds.Contains(config.Key));
+
+ var scopes = relevantConfigurations
+ .Select(config => config.Value.Match(
+ sdJwtConfig => sdJwtConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()),
+ mdDocConfig => mdDocConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString())))
+ .Where(scope => scope.IsSome)
+ .Select(option => option.Fallback(string.Empty));
var tokenRequest = new TokenRequest
{
@@ -376,7 +397,7 @@ public async Task> RequestCredentialSet(Issuance
RedirectUri = session.AuthorizationData.ClientOptions.RedirectUri,
CodeVerifier = session.AuthorizationCodeParameters.Verifier,
Code = issuanceSession.Code,
- Scope = scope.ToNullable(),
+ Scope = string.Join(" ", scopes),
ClientId = session.AuthorizationData.ClientOptions.ClientId
};
@@ -385,132 +406,118 @@ public async Task> RequestCredentialSet(Issuance
session.AuthorizationData.AuthorizationServerMetadata,
session.AuthorizationData.IssuerMetadata.CredentialNonceEndpoint);
- var credentialSet = new CredentialSetRecord();
-
//TODO: Make sure that it does not always request all available credConfigurations
- foreach (var configuration in credConfiguration)
+ var credentialSets = new List();
+ foreach (var configuration in relevantConfigurations)
{
- var validResponse = await _credentialRequestService.RequestCredentials(
+ var validResponses = await _credentialRequestService.RequestCredentials(
configuration,
session.AuthorizationData.IssuerMetadata,
token,
session.AuthorizationData.ClientOptions,
- Option.None);
+ Option.None,
+ session.SpecVersion.ToOption());
var result =
- from response in validResponse
- let cNonce = response.CNonce
- let credentialsOrTransactionId = response.CredentialsOrTransactionId
- select credentialsOrTransactionId.Match(
- async creds =>
- {
- foreach (var credential in creds)
+ from responses in validResponses
+ let credentialSet = new CredentialSetRecord()
+ select
+ from response in responses
+ let cNonce = response.CNonce
+ let credentialsOrTransactionId = response.CredentialsOrTransactionId
+ select credentialsOrTransactionId.Match(
+ async creds =>
{
- await credential.Value.Match(
- async sdJwt =>
+ token = await session.AuthorizationData.IssuerMetadata.CredentialNonceEndpoint.Match(
+ Some: async credentialNonceEndpoint =>
{
- token = await session.AuthorizationData.IssuerMetadata.CredentialNonceEndpoint.Match(
- Some: async credentialNonceEndpoint =>
+ var credentialNonce = await _credentialNonceService.GetCredentialNonce(credentialNonceEndpoint);
+ return token.Match>(
+ oAuth => oAuth with { CNonce = credentialNonce.Value },
+ dPop => dPop with
{
- var credentialNonce = await _credentialNonceService.GetCredentialNonce(credentialNonceEndpoint);
- return token.Match>(
- oAuth => oAuth with { CNonce = credentialNonce.Value },
- dPop => dPop with
- {
- Token = dPop.Token with { CNonce = credentialNonce.Value }
- });
- },
- None: () =>
- {
- return Task.FromResult>( token.Match>(
- oAuth => oAuth with { CNonce = cNonce.ToNullable() },
- dPop => dPop with
- {
- Token = dPop.Token with { CNonce = cNonce.ToNullable() }
- }));
+ Token = dPop.Token with { CNonce = credentialNonce.Value }
});
-
- var record = sdJwt.Decoded.ToRecord(
- configuration.AsT0,
- response.KeyId,
- credentialSet.CredentialSetId,
- creds.Count > 1);
-
- await _sdJwtService.AddAsync(context, record);
-
- credentialSet.AddSdJwtData(record);
},
- async mdoc =>
+ None: () =>
{
- token = await session.AuthorizationData.IssuerMetadata.CredentialNonceEndpoint.Match(
- Some: async credentialNonceEndpoint =>
- {
- var credentialNonce = await _credentialNonceService.GetCredentialNonce(credentialNonceEndpoint);
- return token.Match>(
- oAuth => oAuth with { CNonce = credentialNonce.Value },
- dPop => dPop with
- {
- Token = dPop.Token with { CNonce = credentialNonce.Value }
- });
- },
- None: () =>
+ return Task.FromResult>( token.Match>(
+ oAuth => oAuth with { CNonce = cNonce.ToNullable() },
+ dPop => dPop with
{
- return Task.FromResult>( token.Match>(
- oAuth => oAuth with { CNonce = cNonce.ToNullable() },
- dPop => dPop with
- {
- Token = dPop.Token with { CNonce = cNonce.ToNullable() }
- }));
- });
-
- var displays = MdocFun.CreateMdocDisplays(configuration.AsT1);
-
- var record = mdoc.Decoded.ToRecord(
- displays,
- response.KeyId,
- credentialSet.CredentialSetId,
- creds.Count > 1);
-
- await _mdocStorage.Add(record);
-
- credentialSet.AddMDocData(record, session.AuthorizationData.IssuerMetadata.CredentialIssuer);
- });
- }
- },
- // ReSharper disable once UnusedParameter.Local
- transactionId => throw new NotImplementedException());
-
- await result.OnSuccess(task => task);
+ Token = dPop.Token with { CNonce = cNonce.ToNullable() }
+ }));
+ });
+
+ foreach (var credential in creds)
+ {
+ await credential.Value.Match(
+ async sdJwt =>
+ {
+ var record = sdJwt.Decoded.ToRecord(
+ configuration.Value.AsT0,
+ response.KeyId,
+ credentialSet.CredentialSetId,
+ creds.Count > 1);
+
+ await _sdJwtService.AddAsync(context, record);
+
+ credentialSet.AddSdJwtData(record);
+ // credentialSets.Add(credentialSet);
+ },
+ async mdoc =>
+ {
+ var displays = MdocFun.CreateMdocDisplays(configuration.Value.AsT1);
+
+ var record = mdoc.Decoded.ToRecord(
+ displays,
+ response.KeyId,
+ credentialSet.CredentialSetId,
+ creds.Count > 1);
+
+ await _mdocStorage.Add(record);
+
+ credentialSet.AddMDocData(record, session.AuthorizationData.IssuerMetadata.CredentialIssuer);
+ });
+ }
+ credentialSets.Add(credentialSet);
+ },
+ // ReSharper disable once UnusedParameter.Local
+ transactionId => throw new NotImplementedException());
+
+ await result.OnSuccess(async tasks => await Task.WhenAll(tasks));
}
- await _credentialSetStorage.Add(credentialSet);
+ foreach (var credentialSet in credentialSets)
+ {
+ await _credentialSetStorage.Add(credentialSet);
+ }
await _authFlowSessionStorage.DeleteAsync(context, session.AuthFlowSessionState);
- return credentialSet;
+ return credentialSets;
}
//TODO: Refactor this C'' method into current flows (too much duplicate code)
///
- public async Task> RequestOnDemandCredentialSet(IssuanceSession issuanceSession, AuthorizationRequest authorizationRequest, OneOf credentialType)
+ public async Task>> RequestOnDemandCredentialSet(IssuanceSession issuanceSession, AuthorizationRequest authorizationRequest)
{
var context = await _agentProvider.GetContextAsync();
var session = await _authFlowSessionStorage.GetAsync(context, issuanceSession.AuthFlowSessionState);
- var credConfigurations = session
+ var relevantConfigurations = session
.AuthorizationData
.IssuerMetadata
.CredentialConfigurationsSupported
- .Where(config => session.AuthorizationData.CredentialConfigurationIds.Contains(config.Key))
- .Select(pair => pair.Value);
+ .Where(config => session.AuthorizationData.CredentialConfigurationIds.Contains(config.Key));
- var scope = session
- .AuthorizationData
- .IssuerMetadata
- .CredentialConfigurationsSupported.First().Value.Match(
- sdJwtConfig => sdJwtConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()),
- mdDocConfig => mdDocConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()));
+ var scopes = relevantConfigurations
+ .Select(config => config.Value.Match(
+ sdJwtConfig => sdJwtConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()),
+ mdDocConfig => mdDocConfig.CredentialConfiguration.Scope.OnSome(scope => scope.ToString())))
+ .Where(scope => scope.IsSome)
+ .Select(option => option.Fallback(string.Empty));
var tokenRequest = new TokenRequest
{
@@ -518,7 +525,7 @@ public async Task> RequestOnDemandCredentialSe
RedirectUri = session.AuthorizationData.ClientOptions.RedirectUri,
CodeVerifier = session.AuthorizationCodeParameters.Verifier,
Code = issuanceSession.Code,
- Scope = scope.ToNullable(),
+ Scope = string.Join(" ", scopes),
ClientId = session.AuthorizationData.ClientOptions.ClientId
};
@@ -527,120 +534,130 @@ public async Task> RequestOnDemandCredentialSe
session.AuthorizationData.AuthorizationServerMetadata,
session.AuthorizationData.IssuerMetadata.CredentialNonceEndpoint);
- var credentialConfigs = credentialType.Match(
- vct => credConfigurations.Where(config => config.Match(
- sdJwtConfiguration => sdJwtConfiguration.Vct == vct,
- _ => false)),
- docType => credConfigurations.Where(config => config.Match(
- _ => false,
- mDocConfiguration => mDocConfiguration.DocType == docType)));
-
- List credentials = new();
- var credentialSetRecord = new CredentialSetRecord();
+ var credentials = new List<(CredentialSetRecord, List)>();
//TODO: Make sure that it does not always request all available credConfigurations
- foreach (var configuration in credentialConfigs)
+ foreach (var configuration in relevantConfigurations)
{
- var validResponse = await _credentialRequestService.RequestCredentials(
+ var validResponses = await _credentialRequestService.RequestCredentials(
configuration,
session.AuthorizationData.IssuerMetadata,
token,
session.AuthorizationData.ClientOptions,
- authorizationRequest
- );
+ authorizationRequest,
+ session.SpecVersion.ToOption());
var result =
- from response in validResponse
- let cNonce = response.CNonce
- let credentialsOrTransactionId = response.CredentialsOrTransactionId
- select credentialsOrTransactionId.Match, TransactionId>>(
- creds =>
- {
- var records = new List();
- foreach (var credential in creds)
+ from responses in validResponses
+ let credentialSet = new CredentialSetRecord()
+ select
+ from response in responses
+ let cNonce = response.CNonce
+ let credentialsOrTransactionId = response.CredentialsOrTransactionId
+ select credentialsOrTransactionId.Match(
+ async creds =>
{
- var record = credential.Value.Match(
- sdJwt =>
- {
- var record = sdJwt.Decoded.ToRecord(
- configuration.AsT0,
- response.KeyId,
- credentialSetRecord.CredentialSetId,
- creds.Count > 1);
-
- credentialSetRecord.AddSdJwtData(record);
-
- token = token.Match>(
- oAuth =>
- {
- session.AuthorizationData = session.AuthorizationData with
+ token = await session.AuthorizationData.IssuerMetadata.CredentialNonceEndpoint.Match(
+ Some: async credentialNonceEndpoint =>
+ {
+ var credentialNonce = await _credentialNonceService.GetCredentialNonce(credentialNonceEndpoint);
+ return token.Match>(
+ oAuth =>
{
- OAuthToken = oAuth
- };
- return oAuth with { CNonce = cNonce.ToNullable() };
- },
- dPop =>
- {
- session.AuthorizationData = session.AuthorizationData with
+ session.AuthorizationData = session.AuthorizationData with
+ {
+ OAuthToken = oAuth
+ };
+
+ return oAuth with
+ {
+ CNonce = credentialNonce.Value
+ };
+ },
+ dPop =>
+ {
+ session.AuthorizationData = session.AuthorizationData with
+ {
+ OAuthToken = dPop.Token
+ };
+
+ return dPop with
+ {
+ Token = dPop.Token with { CNonce = credentialNonce.Value }
+ };
+ });
+ },
+ None: () =>
+ {
+ return Task.FromResult>( token.Match>(
+ oAuth =>
{
- OAuthToken = dPop.Token
- };
- return dPop with { Token = dPop.Token with { CNonce = cNonce.ToNullable() } };
- });
-
- return record;
- },
- mdoc =>
+ session.AuthorizationData = session.AuthorizationData with
+ {
+ OAuthToken = oAuth
+ };
+
+ return oAuth with { CNonce = cNonce.ToNullable() };
+ },
+ dPop =>
+ {
+ session.AuthorizationData = session.AuthorizationData with
+ {
+ OAuthToken = dPop.Token
+ };
+
+ return dPop with
+ {
+ Token = dPop.Token with { CNonce = cNonce.ToNullable() }
+ };
+ }));
+ });
+
+ var records = new List();
+ foreach (var credential in creds)
{
- var displays = MdocFun.CreateMdocDisplays(configuration.AsT1);
-
- var record = mdoc.Decoded.ToRecord(
- displays,
- response.KeyId,
- credentialSetRecord.CredentialSetId,
- creds.Count > 1);
+ var record = credential.Value.Match(
+ sdJwt =>
+ {
+ var record = sdJwt.Decoded.ToRecord(
+ configuration.Value.AsT0,
+ response.KeyId,
+ credentialSet.CredentialSetId,
+ creds.Count > 1);
- credentialSetRecord.AddMDocData(record, session.AuthorizationData.IssuerMetadata.CredentialIssuer);
+ credentialSet.AddSdJwtData(record);
- token = token.Match>(
- oAuth =>
- {
- session.AuthorizationData = session.AuthorizationData with
- {
- OAuthToken = oAuth
- };
- return oAuth with { CNonce = cNonce.ToNullable() };
- },
- dPop =>
- {
- session.AuthorizationData = session.AuthorizationData with
- {
- OAuthToken = dPop.Token
- };
- return dPop with { Token = dPop.Token with { CNonce = cNonce.ToNullable() } };
- });
+ return record;
+ },
+ mdoc =>
+ {
+ var displays = MdocFun.CreateMdocDisplays(configuration.Value.AsT1);
+
+ var record = mdoc.Decoded.ToRecord(
+ displays,
+ response.KeyId,
+ credentialSet.CredentialSetId,
+ creds.Count > 1);
- return record;
- });
-
- records.Add(record);
- }
+ credentialSet.AddMDocData(record, session.AuthorizationData.IssuerMetadata.CredentialIssuer);
- return records;
- },
- // ReSharper disable once UnusedParameter.Local
- transactionId => throw new NotImplementedException());
+ return record;
+ });
+
+ records.Add(record);
+ }
+
+ credentials.Add((credentialSet, records));
+ },
+ // ReSharper disable once UnusedParameter.Local
+ transactionId => throw new NotImplementedException());
- result.OnSuccess(task =>
- {
- credentials.AddRange((List)task.Value);
- return Unit.Default;
- });
+ await result.OnSuccess(async tasks => await Task.WhenAll(tasks));
}
await _authFlowSessionStorage.UpdateAsync(context, session);
-
- return new OnDemandCredentialSet(credentialSetRecord, credentials);
+
+ return credentials.Select(credential => new OnDemandCredentialSet(credential.Item1, credential.Item2)).ToList();
}
private static AuthorizationCodeParameters CreateAndStoreCodeChallenge()
diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs
index fc2b1c11..6d3e5ba6 100644
--- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs
+++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs
@@ -51,7 +51,7 @@ public void Can_Encode_To_Json()
var authorizationCodeParameters = new AuthorizationCodeParameters("hello", "world");
var sessionId = AuthFlowSessionState.CreateAuthFlowSessionState();
- var record = new AuthFlowSessionRecord(authorizationData, authorizationCodeParameters, sessionId);
+ var record = new AuthFlowSessionRecord(authorizationData, authorizationCodeParameters, sessionId, 15);
// Act
var recordSut = JObject.FromObject(record);
diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs
index 5bf04c7f..225c86ff 100644
--- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs
+++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs
@@ -37,6 +37,7 @@ public static class AuthFlowSamples
["Verifier"] = "world"
},
["RecordVersion"] = 1,
- ["Id"] = "598e7661-95a8-4531-b707-3d256d3c1745"
+ ["Id"] = "598e7661-95a8-4531-b707-3d256d3c1745",
+ ["spec_version"] = "15"
};
}