Skip to content

Commit

Permalink
Refactor auth flow session handling (#129)
Browse files Browse the repository at this point in the history
* use state paramter in PAR for Auth Code Flow session handling

Signed-off-by: Johannes Tuerk <[email protected]>

* remove scheme check to allow verified deeplinks

Signed-off-by: Johannes Tuerk <[email protected]>

* mdoc oid4vci

Signed-off-by: Kevin <[email protected]>

* separate integration tests

Signed-off-by: Kevin <[email protected]>

* move away from json converters

Signed-off-by: Kevin <[email protected]>

* fix credential request json

Signed-off-by: Kevin <[email protected]>

* align with framework Refactoring

Signed-off-by: Johannes Tuerk <[email protected]>

* remove JsonSettings

Signed-off-by: Johannes Tuerk <[email protected]>

* rename VciSessionState -> AuthCodeSessionState

Signed-off-by: Johannes Tuerk <[email protected]>

---------

Signed-off-by: Johannes Tuerk <[email protected]>
Signed-off-by: Kevin <[email protected]>
Co-authored-by: Kevin <[email protected]>
  • Loading branch information
JoTiTu and Dindexx authored Jul 23, 2024
1 parent f2d4506 commit c5e8be4
Show file tree
Hide file tree
Showing 14 changed files with 108 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ public interface IAuthFlowSessionStorage
/// Deletes the authorization session record by the session identifier.
/// </summary>
/// <param name="context">Agent Context</param>
/// <param name="sessionId">Session Identifier of a Authorization Code Flow session</param>
/// <param name="authFlowSessionState">Session State Identifier of a Authorization Code Flow session</param>
/// <returns></returns>
Task<bool> DeleteAsync(IAgentContext context, VciSessionId sessionId);
Task<bool> DeleteAsync(IAgentContext context, AuthFlowSessionState authFlowSessionState);

/// <summary>
/// Retrieves the authorization session record by the session identifier.
/// </summary>
/// <param name="context">Agent Context</param>
/// <param name="sessionId">Session Identifier of a Authorization Code Flow session</param>
/// <param name="authFlowSessionState">Session State Identifier of a Authorization Code Flow session</param>
/// <returns></returns>
Task<AuthFlowSessionRecord> GetAsync(IAgentContext context, VciSessionId sessionId);
Task<AuthFlowSessionRecord> GetAsync(IAgentContext context, AuthFlowSessionState authFlowSessionState);

/// <summary>
/// Stores the authorization session record.
Expand All @@ -35,11 +35,11 @@ public interface IAuthFlowSessionStorage
/// Parameters required for the authorization during the VCI authorization code
/// flow.
/// </param>
/// <param name="sessionId"></param>
/// <param name="authFlowSessionState">Session State Identifier of a Authorization Code Flow session</param>
/// <returns></returns>
Task<string> StoreAsync(
IAgentContext agentContext,
AuthorizationData authorizationData,
AuthorizationCodeParameters authorizationCodeParameters,
VciSessionId sessionId);
AuthFlowSessionState authFlowSessionState);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using WalletFramework.Core.Functional;

namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Errors;

public record AuthFlowSessionStateError(string Value) : Error($"Invalid AuthFlowSessionState: {Value}");

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,26 @@ public async Task<string> StoreAsync(
IAgentContext agentContext,
AuthorizationData authorizationData,
AuthorizationCodeParameters authorizationCodeParameters,
VciSessionId sessionId)
AuthFlowSessionState authFlowSessionState)
{
var record = new AuthFlowSessionRecord(
authorizationData,
authorizationCodeParameters,
sessionId);
authFlowSessionState);

await _recordService.AddAsync(agentContext.Wallet, record, AuthFlowSessionRecordFun.EncodeToJson);

return record.Id;
}

/// <inheritdoc />
public async Task<AuthFlowSessionRecord> GetAsync(IAgentContext context, VciSessionId sessionId)
public async Task<AuthFlowSessionRecord> GetAsync(IAgentContext context, AuthFlowSessionState authFlowSessionState)
{
var record = await _recordService.GetAsync(context.Wallet, sessionId, AuthFlowSessionRecordFun.DecodeFromJson);
var record = await _recordService.GetAsync(context.Wallet, authFlowSessionState, AuthFlowSessionRecordFun.DecodeFromJson);
return record!;
}

/// <inheritdoc />
public async Task<bool> DeleteAsync(IAgentContext context, VciSessionId sessionId) =>
await _recordService.DeleteAsync<AuthFlowSessionRecord>(context.Wallet, sessionId);
public async Task<bool> DeleteAsync(IAgentContext context, AuthFlowSessionState authFlowSessionState) =>
await _recordService.DeleteAsync<AuthFlowSessionRecord>(context.Wallet, authFlowSessionState);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Globalization;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.Functional;
using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Errors;

namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models;

/// <summary>
/// Identifier of the authorization state during the VCI Authorization Code Flow.
/// </summary>
public struct AuthFlowSessionState
{
/// <summary>
/// Gets the value of the state identifier.
/// </summary>
private string Value { get; }

private AuthFlowSessionState(string value) => Value = value;

/// <summary>
/// Returns the value of the state identifier.
/// </summary>
/// <param name="authFlowSessionState"></param>
/// <returns></returns>
public static implicit operator string(AuthFlowSessionState authFlowSessionState) => authFlowSessionState.Value;

public static Validation<AuthFlowSessionState> ValidAuthFlowSessionState(string authFlowSessionState)
{
if (!Guid.TryParse(authFlowSessionState, out _))
{
return new AuthFlowSessionStateError(authFlowSessionState);
}

return new AuthFlowSessionState(authFlowSessionState);
}

public static AuthFlowSessionState CreateAuthFlowSessionState()
{
var guid = Guid.NewGuid().ToString();
return new AuthFlowSessionState(guid);
}
}

public static class AuthFlowSessionStateFun
{
public static AuthFlowSessionState DecodeFromJson(JValue json) => AuthFlowSessionState
.ValidAuthFlowSessionState(json.ToString(CultureInfo.InvariantCulture))
.UnwrapOrThrow(new InvalidOperationException("AuthFlowSessionState is corrupt"));
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ public record IssuanceSession
/// <summary>
/// Gets the session identifier.
/// </summary>
public VciSessionId SessionId { get; }
public AuthFlowSessionState AuthFlowSessionState { get; }

/// <summary>
/// Gets the actual authorization code that is received from the authorization server upon successful authorization.
/// </summary>
public string Code { get; }

private IssuanceSession(VciSessionId sessionId, string code) => (SessionId, Code) = (sessionId, code);
private IssuanceSession(AuthFlowSessionState authFlowSessionState, string code) => (AuthFlowSessionState, Code) = (authFlowSessionState, code);

/// <summary>
/// Creates a new instance of <see cref="IssuanceSession"/> from the given <see cref="Uri"/>.
Expand All @@ -36,9 +36,9 @@ public static IssuanceSession FromUri(Uri uri)
throw new InvalidOperationException("Query parameter 'code' is missing");
}

var sessionIdParam = queryParams.Get("session");
var sessionId = VciSessionId.ValidSessionId(sessionIdParam).Fallback(VciSessionId.CreateSessionId());
var sessionStateParam = queryParams.Get("state");
var authFlowSessionState = AuthFlowSessionState.ValidAuthFlowSessionState(sessionStateParam).Fallback(AuthFlowSessionState.CreateAuthFlowSessionState());

return new IssuanceSession(sessionId, code);
return new IssuanceSession(authFlowSessionState, code);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ internal record PushedAuthorizationRequest

[JsonProperty("code_challenge_method")]
public string CodeChallengeMethod { get; }

[JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)]
public string AuthFlowSessionState { get; }

[JsonProperty("authorization_details", NullValueHandling = NullValueHandling.Ignore)]
public AuthorizationDetails[]? AuthorizationDetails { get; }
Expand All @@ -39,7 +42,7 @@ internal record PushedAuthorizationRequest
public string? Resource { get; }

public PushedAuthorizationRequest(
VciSessionId sessionId,
AuthFlowSessionState authFlowSessionState,
ClientOptions clientOptions,
AuthorizationCodeParameters authorizationCodeParameters,
AuthorizationDetails[]? authorizationDetails,
Expand All @@ -49,10 +52,11 @@ public PushedAuthorizationRequest(
string? resource)
{
ClientId = clientOptions.ClientId;
RedirectUri = clientOptions.RedirectUri + "?session=" + sessionId;
RedirectUri = clientOptions.RedirectUri;
WalletIssuer = clientOptions.WalletIssuer;
CodeChallenge = authorizationCodeParameters.Challenge;
CodeChallengeMethod = authorizationCodeParameters.CodeChallengeMethod;
AuthFlowSessionState = authFlowSessionState;
AuthorizationDetails = authorizationDetails;
IssuerState = issuerState;
UserHint = userHint;
Expand All @@ -79,6 +83,9 @@ public FormUrlEncodedContent ToFormUrlEncoded()
if (!string.IsNullOrEmpty(CodeChallengeMethod))
keyValuePairs.Add(new KeyValuePair<string, string>("code_challenge_method", CodeChallengeMethod));

if (!string.IsNullOrEmpty(AuthFlowSessionState))
keyValuePairs.Add(new KeyValuePair<string, string>("state", AuthFlowSessionState));

if (AuthorizationDetails != null)
keyValuePairs.Add(new KeyValuePair<string, string>("authorization_details", SerializeObject(AuthorizationDetails)));

Expand Down
49 changes: 0 additions & 49 deletions src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/VciSessionId.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records;
public sealed class AuthFlowSessionRecord : RecordBase
{
/// <summary>
/// The session specific id.
/// The session specific state id.
/// </summary>
[JsonIgnore]
public VciSessionId SessionId
public AuthFlowSessionState AuthFlowSessionState
{
get => VciSessionId
.ValidSessionId(Id)
.UnwrapOrThrow(new InvalidOperationException("SessionId is corrupt"));
get => AuthFlowSessionState
.ValidAuthFlowSessionState(Id)
.UnwrapOrThrow(new InvalidOperationException("AuthFlowSessionState is corrupt"));
set
{
string str = value;
Expand Down Expand Up @@ -56,13 +56,13 @@ public AuthFlowSessionRecord()
/// </summary>
/// <param name="authorizationData"></param>
/// <param name="authorizationCodeParameters"></param>
/// <param name="sessionId"></param>
/// <param name="authFlowSessionState"></param>
public AuthFlowSessionRecord(
AuthorizationData authorizationData,
AuthorizationCodeParameters authorizationCodeParameters,
VciSessionId sessionId)
AuthFlowSessionState authFlowSessionState)
{
SessionId = sessionId;
AuthFlowSessionState = authFlowSessionState;
RecordVersion = 1;
AuthorizationCodeParameters = authorizationCodeParameters;
AuthorizationData = authorizationData;
Expand Down Expand Up @@ -90,7 +90,7 @@ public static JObject EncodeToJson(this AuthFlowSessionRecord record)
public static AuthFlowSessionRecord DecodeFromJson(JObject json)
{
var idJson = json[nameof(RecordBase.Id)]!.ToObject<JValue>()!;
var id = VciSessionIdFun.DecodeFromJson(idJson);
var id = AuthFlowSessionStateFun.DecodeFromJson(idJson);

var authCodeParameters = JsonConvert.DeserializeObject<AuthorizationCodeParameters>(
json[AuthorizationCodeParametersJsonKey]!.ToString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ public static Option<AuthorizationCode> OptionalAuthorizationCode(JToken authori
.GetByKey("authorization_server")
.OnSuccess(token => token.ToString())
.ToOption();

if (issuerState.IsNone && authServer.IsNone)
return Option<AuthorizationCode>.None;

return new AuthorizationCode(issuerState, authServer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public Oid4VciClientService(
public async Task<Uri> InitiateAuthFlow(CredentialOfferMetadata offer, ClientOptions clientOptions)
{
var authorizationCodeParameters = CreateAndStoreCodeChallenge();
var sessionId = VciSessionId.CreateSessionId();
var sessionId = AuthFlowSessionState.CreateAuthFlowSessionState();
var issuerMetadata = offer.IssuerMetadata;

var scopes = offer
Expand Down Expand Up @@ -241,7 +241,7 @@ public async Task<Validation<OneOf<SdJwtRecord, MdocRecord>>> RequestCredential(
{
var context = await _agentProvider.GetContextAsync();

var session = await _authFlowSessionStorage.GetAsync(context, issuanceSession.SessionId);
var session = await _authFlowSessionStorage.GetAsync(context, issuanceSession.AuthFlowSessionState);

var credConfiguration = session
.AuthorizationData
Expand All @@ -254,7 +254,7 @@ public async Task<Validation<OneOf<SdJwtRecord, MdocRecord>>> RequestCredential(
var tokenRequest = new TokenRequest
{
GrantType = AuthorizationCodeGrantTypeIdentifier,
RedirectUri = session.AuthorizationData.ClientOptions.RedirectUri + "?session=" + session.SessionId,
RedirectUri = session.AuthorizationData.ClientOptions.RedirectUri,
CodeVerifier = session.AuthorizationCodeParameters.Verifier,
Code = issuanceSession.Code,
ClientId = session.AuthorizationData.ClientOptions.ClientId
Expand All @@ -270,7 +270,7 @@ public async Task<Validation<OneOf<SdJwtRecord, MdocRecord>>> RequestCredential(
token,
session.AuthorizationData.ClientOptions);

await _authFlowSessionStorage.DeleteAsync(context, session.SessionId);
await _authFlowSessionStorage.DeleteAsync(context, session.AuthFlowSessionState);

var result =
from response in validResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,22 @@ public class HaipAuthorizationRequestUri
/// </summary>
public string RequestUri { get; set; } = null!;

/// <summary>
/// Validates the hap conformity of an uri and returns a HaipAuthorizationRequestUri.
/// </summary>
/// <param name="uri"></param>
/// <returns>The HaipAuthorizationRequestUri</returns>
/// <exception cref="InvalidOperationException"></exception>
public static HaipAuthorizationRequestUri FromUri(Uri uri)
{
if (!(uri.Scheme == "haip" | uri.Scheme == "openid4vp"))
throw new InvalidOperationException("Invalid Scheme. Must be haip or openid4vp");

var request = uri.GetQueryParam("request_uri");
if (string.IsNullOrEmpty(request))
throw new InvalidOperationException("HAIP requires request_uri parameter");
/// <summary>
/// Validates the hap conformity of an uri and returns a HaipAuthorizationRequestUri.
/// </summary>
/// <param name="uri"></param>
/// <returns>The HaipAuthorizationRequestUri</returns>
/// <exception cref="InvalidOperationException"></exception>
public static HaipAuthorizationRequestUri FromUri(Uri uri)
{
var request = uri.GetQueryParam("request_uri");
if (string.IsNullOrEmpty(request))
throw new InvalidOperationException("HAIP requires request_uri parameter");

return new HaipAuthorizationRequestUri()
{
RequestUri = request,
Uri = uri
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ private static SdJwtDoc _toSdJwtDoc(SdJwtRecord record)
{
return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~");
}
}
}
Loading

0 comments on commit c5e8be4

Please sign in to comment.