Browse Source

Update the ProcessChallenge/SignIn/SignOut events to expose the host authentication properties

pull/1523/head
Kévin Chalet 3 years ago
parent
commit
43e75cd49f
  1. 2
      sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
  2. 38
      sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
  3. 9
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs
  4. 40
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml
  5. 2
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs
  6. 232
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
  7. 56
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
  8. 14
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  9. 68
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  10. 179
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  11. 150
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  12. 21
      src/OpenIddict.Server/OpenIddictServerEvents.cs
  13. 34
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  14. 79
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  15. 8
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs
  16. 82
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs
  17. 7
      src/OpenIddict.Validation/OpenIddictValidationEvents.cs
  18. 135
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs
  19. 127
      test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

2
sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs

@ -15,7 +15,7 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers
{ {
public class AuthenticationController : Controller public class AuthenticationController : Controller
{ {
[HttpGet, Route("~/login")] [HttpPost, Route("~/login"), ValidateAntiForgeryToken]
public ActionResult LogIn(string provider, string returnUrl) public ActionResult LogIn(string provider, string returnUrl)
{ {
var context = HttpContext.GetOwinContext(); var context = HttpContext.GetOwinContext();

38
sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml

@ -31,6 +31,8 @@
<form action="/logout" method="post"> <form action="/logout" method="post">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<input type="hidden" name="returnUrl" value="@Request.RawUrl" />
<button class="btn btn-lg btn-danger" type="submit">Sign out</button> <button class="btn btn-lg btn-danger" type="submit">Sign out</button>
</form> </form>
} }
@ -38,15 +40,31 @@
else else
{ {
<h1>Welcome, anonymous</h1> <h1>Welcome, anonymous</h1>
@Html.ActionLink("Sign in using the local OIDC server", "Login", "Authentication",
new { provider = "Local" }, new { @class = "btn btn-lg btn-success" }) <form action="/login" method="post">
@Html.ActionLink("Sign in using the local OIDC server (using GitHub delegation)", "Login", "Authentication", @Html.AntiForgeryToken()
new { provider = "Local+GitHub" }, new { @class = "btn btn-lg btn-success" })
@Html.ActionLink("Sign in using GitHub", "Login", "Authentication", <input type="hidden" name="returnUrl" value="@Request.RawUrl" />
new { provider = "GitHub" }, new { @class = "btn btn-lg btn-success" })
@Html.ActionLink("Sign in using Google", "Login", "Authentication", <button class="btn btn-lg btn-success" type="submit" name="provider" value="Local">
new { provider = "Google" }, new { @class = "btn btn-lg btn-success" }) Sign in using the local OIDC server
@Html.ActionLink("Sign in using Twitter", "Login", "Authentication", </button>
new { provider = "Twitter" }, new { @class = "btn btn-lg btn-success" })
<button class="btn btn-lg btn-success" type="submit" name="provider" value="Local+GitHub">
Sign in using the local OIDC server (using GitHub delegation)
</button>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="GitHub">
Sign in using GitHub
</button>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="Google">
Sign in using Google
</button>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="Twitter">
Sign in using Twitter
</button>
</form>
} }
</div> </div>

9
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs

@ -10,7 +10,7 @@ namespace OpenIddict.Sandbox.AspNetCore.Client.Controllers;
public class AuthenticationController : Controller public class AuthenticationController : Controller
{ {
[HttpGet("~/login")] [HttpPost("~/login"), ValidateAntiForgeryToken]
public ActionResult LogIn(string provider, string returnUrl) public ActionResult LogIn(string provider, string returnUrl)
{ {
// Note: OpenIddict always validates the specified provider name when handling the challenge operation, // Note: OpenIddict always validates the specified provider name when handling the challenge operation,
@ -41,7 +41,10 @@ public class AuthenticationController : Controller
// Only allow local return URLs to prevent open redirect attacks. // Only allow local return URLs to prevent open redirect attacks.
RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/", RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/",
Parameters = { [Parameters.IdentityProvider] = "GitHub" } Parameters =
{
[Parameters.IdentityProvider] = "GitHub"
}
}; };
// Ask the OpenIddict client middleware to redirect the user agent to the identity provider. // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
@ -146,7 +149,7 @@ public class AuthenticationController : Controller
// Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (as the // Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (as the
// antiforgery stack requires at least a name claim to bind CSRF cookies to the user's identity) but // antiforgery stack requires at least a name claim to bind CSRF cookies to the user's identity) but
// the access/refresh tokens can be retrieved using result.Properties.GetTokens() to make API calls. // the access/refresh tokens can be retrieved using result.Properties.GetTokens() to make API calls.
if (result.Principal.Identity is not ClaimsIdentity { IsAuthenticated: true }) if (result.Principal is not ClaimsPrincipal { Identity.IsAuthenticated: true })
{ {
throw new InvalidOperationException("The external authorization data cannot be used for authentication."); throw new InvalidOperationException("The external authorization data cannot be used for authentication.");
} }

40
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml

@ -33,17 +33,33 @@
else else
{ {
<h1>Welcome, anonymous</h1> <h1>Welcome, anonymous</h1>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="Local">Sign in using the local OIDC server</a> <form asp-action="Login" asp-controller="Authentication" method="post">
<a class="btn btn-lg btn-success" asp-controller="Authentication" <input type="hidden" name="returnUrl" value="@(Context.Request.PathBase + Context.Request.Path + Context.Request.QueryString)" />
asp-action="Login" asp-route-provider="Local+GitHub">Sign in using the local OIDC server (using GitHub delegation)</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication" <button class="btn btn-lg btn-success" type="submit" name="provider" value="Local">
asp-action="Login" asp-route-provider="GitHub">Sign in using GitHub</a> Sign in using the local OIDC server
<a class="btn btn-lg btn-success" asp-controller="Authentication" </button>
asp-action="Login" asp-route-provider="Google">Sign in using Google</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication" <button class="btn btn-lg btn-success" type="submit" name="provider" value="Local+GitHub">
asp-action="Login" asp-route-provider="Reddit">Sign in using Reddit</a> Sign in using the local OIDC server (using GitHub delegation)
<a class="btn btn-lg btn-success" asp-controller="Authentication" </button>
asp-action="Login" asp-route-provider="Twitter">Sign in using Twitter</a>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="GitHub">
Sign in using GitHub
</button>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="Google">
Sign in using Google
</button>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="Reddit">
Sign in using Reddit
</button>
<button class="btn btn-lg btn-success" type="submit" name="provider" value="Twitter">
Sign in using Twitter
</button>
</form>
} }
</div> </div>

2
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs

@ -45,7 +45,7 @@ public class AuthenticationController : Controller
// Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (as the // Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (as the
// antiforgery stack requires at least a name claim to bind CSRF cookies to the user's identity) but // antiforgery stack requires at least a name claim to bind CSRF cookies to the user's identity) but
// the access/refresh tokens can be retrieved using result.Properties.GetTokens() to make API calls. // the access/refresh tokens can be retrieved using result.Properties.GetTokens() to make API calls.
if (result.Principal.Identity is not ClaimsIdentity { IsAuthenticated: true }) if (result.Principal is not ClaimsPrincipal { Identity.IsAuthenticated: true })
{ {
throw new InvalidOperationException("The external authorization data cannot be used for authentication."); throw new InvalidOperationException("The external authorization data cannot be used for authentication.");
} }

232
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs

@ -42,13 +42,13 @@ public static partial class OpenIddictClientAspNetCoreHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeParameters.Descriptor, ResolveHostChallengeProperties.Descriptor,
GenerateLoginCorrelationCookie.Descriptor, GenerateLoginCorrelationCookie.Descriptor,
/* /*
* Sign-out processing: * Sign-out processing:
*/ */
ResolveHostSignOutParameters.Descriptor, ResolveHostSignOutProperties.Descriptor,
GenerateLogoutCorrelationCookie.Descriptor) GenerateLogoutCorrelationCookie.Descriptor)
.AddRange(Authentication.DefaultHandlers) .AddRange(Authentication.DefaultHandlers)
.AddRange(Session.DefaultHandlers); .AddRange(Session.DefaultHandlers);
@ -414,11 +414,11 @@ public static partial class OpenIddictClientAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Core authentication properties specified by the application that triggered the challenge operation. /// ASP.NET Core authentication properties specified by the application that triggered the challenge operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictClientHandler<ProcessChallengeContext> public class ResolveHostChallengeProperties : IOpenIddictClientHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -426,7 +426,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<ResolveHostChallengeProperties>()
.SetOrder(ValidateChallengeDemand.Descriptor.Order - 500) .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -439,76 +439,74 @@ public static partial class OpenIddictClientAspNetCoreHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is { Items.Count: > 0 })
{
return default;
}
// If an issuer was explicitly set, update the challenge context to use it.
if (properties.Items.TryGetValue(Properties.Issuer, out string? issuer) && !string.IsNullOrEmpty(issuer))
{ {
// Ensure the issuer set by the application is a valid absolute URI. // If an issuer was explicitly set, update the challenge context to use it.
if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) if (properties.Items.TryGetValue(Properties.Issuer, out string? issuer) && !string.IsNullOrEmpty(issuer))
{ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0306)); // Ensure the issuer set by the application is a valid absolute URI.
} if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0306));
}
context.Issuer = uri; context.Issuer = uri;
} }
// If a provider name was explicitly set, update the challenge context to use it. // If a provider name was explicitly set, update the challenge context to use it.
if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) && if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) &&
!string.IsNullOrEmpty(provider)) !string.IsNullOrEmpty(provider))
{ {
context.ProviderName = provider; context.ProviderName = provider;
} }
// If a return URL was specified, use it as the target_link_uri claim. // If a return URL was specified, use it as the target_link_uri claim.
if (!string.IsNullOrEmpty(properties.RedirectUri)) if (!string.IsNullOrEmpty(properties.RedirectUri))
{ {
context.TargetLinkUri = properties.RedirectUri; context.TargetLinkUri = properties.RedirectUri;
} }
// If an identity token hint was specified, attach it to the context. // If an identity token hint was specified, attach it to the context.
if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) && if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) &&
!string.IsNullOrEmpty(token)) !string.IsNullOrEmpty(token))
{ {
context.IdentityTokenHint = token; context.IdentityTokenHint = token;
} }
// If a login hint was specified, attach it to the context. // If a login hint was specified, attach it to the context.
if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) && if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) &&
!string.IsNullOrEmpty(hint)) !string.IsNullOrEmpty(hint))
{ {
context.LoginHint = hint; context.LoginHint = hint;
} }
// Preserve the host properties in the principal. foreach (var property in properties.Items)
if (properties.Items.Count is not 0) {
{ context.Properties[property.Key] = property.Value;
context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); }
} }
foreach (var parameter in properties.Parameters) if (properties is { Parameters.Count: > 0 })
{ {
context.Parameters[parameter.Key] = parameter.Value switch foreach (var parameter in properties.Parameters)
{ {
OpenIddictParameter value => value, context.Parameters[parameter.Key] = parameter.Value switch
JsonElement value => new OpenIddictParameter(value), {
bool value => new OpenIddictParameter(value), OpenIddictParameter value => value,
int value => new OpenIddictParameter(value), JsonElement value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value), bool value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value), int value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value), long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES #if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value), JsonNode value => new OpenIddictParameter(value),
#endif #endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
}; };
}
} }
return default; return default;
@ -590,11 +588,11 @@ public static partial class OpenIddictClientAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-out parameters stored in the ASP.NET /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Core authentication properties specified by the application that triggered the sign-out operation. /// ASP.NET Core authentication properties specified by the application that triggered the sign-out operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostSignOutParameters : IOpenIddictClientHandler<ProcessSignOutContext> public class ResolveHostSignOutProperties : IOpenIddictClientHandler<ProcessSignOutContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -602,7 +600,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostSignOutParameters>() .UseSingletonHandler<ResolveHostSignOutProperties>()
.SetOrder(ValidateSignOutDemand.Descriptor.Order - 500) .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -615,76 +613,74 @@ public static partial class OpenIddictClientAspNetCoreHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is { Items.Count: > 0 })
{
return default;
}
// If an issuer was explicitly set, update the sign-out context to use it.
if (properties.Items.TryGetValue(Properties.Issuer, out string? issuer) && !string.IsNullOrEmpty(issuer))
{ {
// Ensure the issuer set by the application is a valid absolute URI. // If an issuer was explicitly set, update the sign-out context to use it.
if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) if (properties.Items.TryGetValue(Properties.Issuer, out string? issuer) && !string.IsNullOrEmpty(issuer))
{ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0306)); // Ensure the issuer set by the application is a valid absolute URI.
} if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0306));
}
context.Issuer = uri; context.Issuer = uri;
} }
// If a provider name was explicitly set, update the sign-out context to use it. // If a provider name was explicitly set, update the sign-out context to use it.
if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) && if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) &&
!string.IsNullOrEmpty(provider)) !string.IsNullOrEmpty(provider))
{ {
context.ProviderName = provider; context.ProviderName = provider;
} }
// If a return URL was specified, use it as the target_link_uri claim. // If a return URL was specified, use it as the target_link_uri claim.
if (!string.IsNullOrEmpty(properties.RedirectUri)) if (!string.IsNullOrEmpty(properties.RedirectUri))
{ {
context.TargetLinkUri = properties.RedirectUri; context.TargetLinkUri = properties.RedirectUri;
} }
// If an identity token hint was specified, attach it to the context. // If an identity token hint was specified, attach it to the context.
if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) && if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) &&
!string.IsNullOrEmpty(token)) !string.IsNullOrEmpty(token))
{ {
context.IdentityTokenHint = token; context.IdentityTokenHint = token;
} }
// If a login hint was specified, attach it to the context. // If a login hint was specified, attach it to the context.
if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) && if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) &&
!string.IsNullOrEmpty(hint)) !string.IsNullOrEmpty(hint))
{ {
context.LoginHint = hint; context.LoginHint = hint;
} }
// Preserve the host properties in the principal. foreach (var property in properties.Items)
if (properties.Items.Count is not 0) {
{ context.Properties[property.Key] = property.Value;
context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); }
} }
foreach (var parameter in properties.Parameters) if (properties is { Parameters.Count: > 0 })
{ {
context.Parameters[parameter.Key] = parameter.Value switch foreach (var parameter in properties.Parameters)
{ {
OpenIddictParameter value => value, context.Parameters[parameter.Key] = parameter.Value switch
JsonElement value => new OpenIddictParameter(value), {
bool value => new OpenIddictParameter(value), OpenIddictParameter value => value,
int value => new OpenIddictParameter(value), JsonElement value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value), bool value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value), int value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value), long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES #if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value), JsonNode value => new OpenIddictParameter(value),
#endif #endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
}; };
}
} }
return default; return default;

56
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs

@ -38,13 +38,13 @@ public static partial class OpenIddictClientOwinHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeParameters.Descriptor, ResolveHostChallengeProperties.Descriptor,
GenerateLoginCorrelationCookie.Descriptor, GenerateLoginCorrelationCookie.Descriptor,
/* /*
* Sign-out processing: * Sign-out processing:
*/ */
ResolveHostSignOutParameters.Descriptor, ResolveHostSignOutProperties.Descriptor,
GenerateLogoutCorrelationCookie.Descriptor) GenerateLogoutCorrelationCookie.Descriptor)
.AddRange(Authentication.DefaultHandlers) .AddRange(Authentication.DefaultHandlers)
.AddRange(Session.DefaultHandlers); .AddRange(Session.DefaultHandlers);
@ -319,7 +319,7 @@ public static partial class OpenIddictClientOwinHandlers
/// <summary> /// <summary>
/// Contains the logic responsible for comparing the current request URL to the expected URL stored in the state token. /// Contains the logic responsible for comparing the current request URL to the expected URL stored in the state token.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ValidateEndpointUri : IOpenIddictClientHandler<ProcessAuthenticationContext> public class ValidateEndpointUri : IOpenIddictClientHandler<ProcessAuthenticationContext>
{ {
@ -425,11 +425,11 @@ public static partial class OpenIddictClientOwinHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Core authentication properties specified by the application that triggered the challenge operation. /// OWIN authentication properties specified by the application that triggered the challenge operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictClientHandler<ProcessChallengeContext> public class ResolveHostChallengeProperties : IOpenIddictClientHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -437,7 +437,7 @@ public static partial class OpenIddictClientOwinHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<ResolveHostChallengeProperties>()
.SetOrder(ValidateChallengeDemand.Descriptor.Order - 500) .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -450,10 +450,8 @@ public static partial class OpenIddictClientOwinHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not { Dictionary.Count: > 0 })
{ {
return default; return default;
} }
@ -497,13 +495,7 @@ public static partial class OpenIddictClientOwinHandlers
context.LoginHint = hint; context.LoginHint = hint;
} }
// Preserve the host properties in the principal. // Note: unlike ASP.NET Core, OWIN's AuthenticationProperties doesn't offer a strongly-typed
if (properties.Dictionary.Count is not 0)
{
context.Principal.SetClaim(Claims.Private.HostProperties, properties.Dictionary);
}
// Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed
// dictionary that allows flowing parameters while preserving their original types. To allow // dictionary that allows flowing parameters while preserving their original types. To allow
// returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary
// but requires suffixing the properties that are meant to be used as parameters using a special // but requires suffixing the properties that are meant to be used as parameters using a special
@ -539,6 +531,11 @@ public static partial class OpenIddictClientOwinHandlers
{ {
context.Parameters[name] = value; context.Parameters[name] = value;
} }
else
{
context.Properties[property.Key] = property.Value;
}
} }
return default; return default;
@ -628,11 +625,11 @@ public static partial class OpenIddictClientOwinHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-out parameters stored in the ASP.NET /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Core authentication properties specified by the application that triggered the sign-out operation. /// OWIN authentication properties specified by the application that triggered the sign-out operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ResolveHostSignOutParameters : IOpenIddictClientHandler<ProcessSignOutContext> public class ResolveHostSignOutProperties : IOpenIddictClientHandler<ProcessSignOutContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -640,7 +637,7 @@ public static partial class OpenIddictClientOwinHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostSignOutParameters>() .UseSingletonHandler<ResolveHostSignOutProperties>()
.SetOrder(ValidateSignOutDemand.Descriptor.Order - 500) .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -653,10 +650,8 @@ public static partial class OpenIddictClientOwinHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not { Dictionary.Count: > 0 })
{ {
return default; return default;
} }
@ -700,13 +695,7 @@ public static partial class OpenIddictClientOwinHandlers
context.LoginHint = hint; context.LoginHint = hint;
} }
// Preserve the host properties in the principal. // Note: unlike ASP.NET Core, OWIN's AuthenticationProperties doesn't offer a strongly-typed
if (properties.Dictionary.Count is not 0)
{
context.Principal.SetClaim(Claims.Private.HostProperties, properties.Dictionary);
}
// Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed
// dictionary that allows flowing parameters while preserving their original types. To allow // dictionary that allows flowing parameters while preserving their original types. To allow
// returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary
// but requires suffixing the properties that are meant to be used as parameters using a special // but requires suffixing the properties that are meant to be used as parameters using a special
@ -742,6 +731,11 @@ public static partial class OpenIddictClientOwinHandlers
{ {
context.Parameters[name] = value; context.Parameters[name] = value;
} }
else
{
context.Properties[property.Key] = property.Value;
}
} }
return default; return default;

14
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -693,6 +693,11 @@ public static partial class OpenIddictClientEvents
set => Transaction.Response = value; set => Transaction.Response = value;
} }
/// <summary>
/// Gets the user-defined authentication properties, if available.
/// </summary>
public Dictionary<string, string?> Properties { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets or sets the name of the provider that will be /// Gets or sets the name of the provider that will be
/// used to resolve the issuer identity, if applicable. /// used to resolve the issuer identity, if applicable.
@ -700,7 +705,7 @@ public static partial class OpenIddictClientEvents
public string? ProviderName { get; set; } public string? ProviderName { get; set; }
/// <summary> /// <summary>
/// Gets the additional parameters returned to caller. /// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>
public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal); public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal);
@ -843,6 +848,11 @@ public static partial class OpenIddictClientEvents
set => Transaction.Response = value; set => Transaction.Response = value;
} }
/// <summary>
/// Gets the user-defined authentication properties, if available.
/// </summary>
public Dictionary<string, string?> Properties { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets or sets the name of the provider that will be /// Gets or sets the name of the provider that will be
/// used to resolve the issuer identity, if applicable. /// used to resolve the issuer identity, if applicable.
@ -884,7 +894,7 @@ public static partial class OpenIddictClientEvents
public string? RequestForgeryProtection { get; set; } public string? RequestForgeryProtection { get; set; }
/// <summary> /// <summary>
/// Gets the additional parameters returned to caller. /// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>
public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal); public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal);

68
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -94,6 +94,7 @@ public static partial class OpenIddictClientHandlers
ResolveClientRegistrationFromChallengeContext.Descriptor, ResolveClientRegistrationFromChallengeContext.Descriptor,
AttachGrantType.Descriptor, AttachGrantType.Descriptor,
EvaluateGeneratedChallengeTokens.Descriptor, EvaluateGeneratedChallengeTokens.Descriptor,
AttachChallengeHostProperties.Descriptor,
AttachResponseType.Descriptor, AttachResponseType.Descriptor,
AttachResponseMode.Descriptor, AttachResponseMode.Descriptor,
AttachClientId.Descriptor, AttachClientId.Descriptor,
@ -116,6 +117,7 @@ public static partial class OpenIddictClientHandlers
AttachOptionalClientId.Descriptor, AttachOptionalClientId.Descriptor,
AttachPostLogoutRedirectUri.Descriptor, AttachPostLogoutRedirectUri.Descriptor,
EvaluateGeneratedLogoutTokens.Descriptor, EvaluateGeneratedLogoutTokens.Descriptor,
AttachSignOutHostProperties.Descriptor,
AttachLogoutRequestForgeryProtection.Descriptor, AttachLogoutRequestForgeryProtection.Descriptor,
PrepareLogoutStateTokenPrincipal.Descriptor, PrepareLogoutStateTokenPrincipal.Descriptor,
GenerateLogoutStateToken.Descriptor, GenerateLogoutStateToken.Descriptor,
@ -3567,6 +3569,37 @@ public static partial class OpenIddictClientHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for attaching the user-defined properties to the authentication principal.
/// </summary>
public class AttachChallengeHostProperties : IOpenIddictClientHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.UseSingletonHandler<AttachChallengeHostProperties>()
.SetOrder(EvaluateGeneratedChallengeTokens.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
context.Principal.SetClaim(Claims.Private.HostProperties, context.Properties);
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the response type to the challenge request. /// Contains the logic responsible for attaching the response type to the challenge request.
/// </summary> /// </summary>
@ -3579,7 +3612,7 @@ public static partial class OpenIddictClientHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireInteractiveGrantType>() .AddFilter<RequireInteractiveGrantType>()
.UseSingletonHandler<AttachResponseType>() .UseSingletonHandler<AttachResponseType>()
.SetOrder(EvaluateGeneratedChallengeTokens.Descriptor.Order + 1_000) .SetOrder(AttachChallengeHostProperties.Descriptor.Order + 1_000)
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>
@ -4646,6 +4679,37 @@ public static partial class OpenIddictClientHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for attaching the user-defined properties to the authentication principal.
/// </summary>
public class AttachSignOutHostProperties : IOpenIddictClientHandler<ProcessSignOutContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.UseSingletonHandler<AttachSignOutHostProperties>()
.SetOrder(EvaluateGeneratedLogoutTokens.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
context.Principal.SetClaim(Claims.Private.HostProperties, context.Properties);
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching a request forgery protection to the authorization request. /// Contains the logic responsible for attaching a request forgery protection to the authorization request.
/// </summary> /// </summary>
@ -4657,7 +4721,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.UseSingletonHandler<AttachLogoutRequestForgeryProtection>() .UseSingletonHandler<AttachLogoutRequestForgeryProtection>()
.SetOrder(EvaluateGeneratedLogoutTokens.Descriptor.Order + 1_000) .SetOrder(AttachSignOutHostProperties.Descriptor.Order + 1_000)
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>

179
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -7,7 +7,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Claims;
using System.Text; using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
@ -38,18 +37,18 @@ public static partial class OpenIddictServerAspNetCoreHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeProperties.Descriptor,
AttachHostChallengeError.Descriptor, AttachHostChallengeError.Descriptor,
ResolveHostChallengeParameters.Descriptor,
/* /*
* Sign-in processing: * Sign-in processing:
*/ */
ResolveHostSignInParameters.Descriptor, ResolveHostSignInProperties.Descriptor,
/* /*
* Sign-out processing: * Sign-out processing:
*/ */
ResolveHostSignOutParameters.Descriptor) ResolveHostSignOutProperties.Descriptor)
.AddRange(Authentication.DefaultHandlers) .AddRange(Authentication.DefaultHandlers)
.AddRange(Device.DefaultHandlers) .AddRange(Device.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers) .AddRange(Discovery.DefaultHandlers)
@ -279,10 +278,11 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties. /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// ASP.NET Core authentication properties specified by the application that triggered the challenge operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class AttachHostChallengeError : IOpenIddictServerHandler<ProcessChallengeContext> public class ResolveHostChallengeProperties : IOpenIddictServerHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -290,8 +290,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<AttachHostChallengeError>() .UseSingletonHandler<ResolveHostChallengeProperties>()
.SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -304,12 +304,34 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is not null) if (properties is { Items.Count: > 0 })
{ {
context.Response.Error = properties.GetString(Properties.Error); foreach (var property in properties.Items)
context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription); {
context.Response.ErrorUri = properties.GetString(Properties.ErrorUri); context.Properties[property.Key] = property.Value;
context.Response.Scope = properties.GetString(Properties.Scope); }
}
if (properties is { Parameters.Count: > 0 })
{
foreach (var parameter in properties.Parameters)
{
context.Parameters[parameter.Key] = parameter.Value switch
{
OpenIddictParameter value => value,
JsonElement value => new OpenIddictParameter(value),
bool value => new OpenIddictParameter(value),
int value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}
} }
return default; return default;
@ -317,11 +339,10 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET /// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties.
/// Core authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictServerHandler<ProcessChallengeContext> public class AttachHostChallengeError : IOpenIddictServerHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -329,8 +350,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(AttachCustomChallengeParameters.Descriptor.Order - 500) .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -343,28 +364,12 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not null)
{
return default;
}
foreach (var parameter in properties.Parameters)
{ {
context.Parameters[parameter.Key] = parameter.Value switch context.Response.Error = properties.GetString(Properties.Error);
{ context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription);
OpenIddictParameter value => value, context.Response.ErrorUri = properties.GetString(Properties.ErrorUri);
JsonElement value => new OpenIddictParameter(value), context.Response.Scope = properties.GetString(Properties.Scope);
bool value => new OpenIddictParameter(value),
int value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
} }
return default; return default;
@ -372,11 +377,11 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Core authentication properties specified by the application that triggered the sign-in operation. /// ASP.NET Core authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostSignInParameters : IOpenIddictServerHandler<ProcessSignInContext> public class ResolveHostSignInProperties : IOpenIddictServerHandler<ProcessSignInContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -384,8 +389,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostSignInParameters>() .UseSingletonHandler<ResolveHostSignInProperties>()
.SetOrder(AttachCustomSignInParameters.Descriptor.Order - 500) .SetOrder(ValidateSignInDemand.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -397,37 +402,35 @@ public static partial class OpenIddictServerAspNetCoreHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is { Items.Count: > 0 })
{ {
return default; foreach (var property in properties.Items)
} {
context.Properties[property.Key] = property.Value;
// Preserve the host properties in the principal. }
if (properties.Items.Count is not 0)
{
context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items);
} }
foreach (var parameter in properties.Parameters) if (properties is { Parameters.Count: > 0 })
{ {
context.Parameters[parameter.Key] = parameter.Value switch foreach (var parameter in properties.Parameters)
{ {
OpenIddictParameter value => value, context.Parameters[parameter.Key] = parameter.Value switch
JsonElement value => new OpenIddictParameter(value), {
bool value => new OpenIddictParameter(value), OpenIddictParameter value => value,
int value => new OpenIddictParameter(value), JsonElement value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value), bool value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value), int value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value), long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES #if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value), JsonNode value => new OpenIddictParameter(value),
#endif #endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
}; };
}
} }
return default; return default;
@ -435,11 +438,11 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-out parameters stored in the ASP.NET /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Core authentication properties specified by the application that triggered the sign-out operation. /// ASP.NET Core authentication properties specified by the application that triggered the sign-out operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostSignOutParameters : IOpenIddictServerHandler<ProcessSignOutContext> public class ResolveHostSignOutProperties : IOpenIddictServerHandler<ProcessSignOutContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -447,8 +450,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignOutContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostSignOutParameters>() .UseSingletonHandler<ResolveHostSignOutProperties>()
.SetOrder(AttachCustomSignOutParameters.Descriptor.Order - 500) .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -461,28 +464,34 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is { Items.Count: > 0 })
{ {
return default; foreach (var property in properties.Items)
{
context.Properties[property.Key] = property.Value;
}
} }
foreach (var parameter in properties.Parameters) if (properties is { Parameters.Count: > 0 })
{ {
context.Parameters[parameter.Key] = parameter.Value switch foreach (var parameter in properties.Parameters)
{ {
OpenIddictParameter value => value, context.Parameters[parameter.Key] = parameter.Value switch
JsonElement value => new OpenIddictParameter(value), {
bool value => new OpenIddictParameter(value), OpenIddictParameter value => value,
int value => new OpenIddictParameter(value), JsonElement value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value), bool value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value), int value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value), long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES #if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value), JsonNode value => new OpenIddictParameter(value),
#endif #endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
}; };
}
} }
return default; return default;

150
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -8,7 +8,6 @@ using System.Collections.Immutable;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Security.Claims;
using System.Text; using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
@ -34,18 +33,18 @@ public static partial class OpenIddictServerOwinHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeProperties.Descriptor,
AttachHostChallengeError.Descriptor, AttachHostChallengeError.Descriptor,
ResolveHostChallengeParameters.Descriptor,
/* /*
* Sign-in processing: * Sign-in processing:
*/ */
ResolveHostSignInParameters.Descriptor, ResolveHostSignInProperties.Descriptor,
/* /*
* Sign-out processing: * Sign-out processing:
*/ */
ResolveHostSignOutParameters.Descriptor) ResolveHostSignOutProperties.Descriptor)
.AddRange(Authentication.DefaultHandlers) .AddRange(Authentication.DefaultHandlers)
.AddRange(Device.DefaultHandlers) .AddRange(Device.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers) .AddRange(Discovery.DefaultHandlers)
@ -277,52 +276,11 @@ public static partial class OpenIddictServerOwinHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the error details using the OWIN authentication properties. /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// OWIN authentication properties specified by the application that triggered the challenge operation.
/// </summary>
public class AttachHostChallengeError : IOpenIddictServerHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is not null)
{
context.Response.Error = GetProperty(properties, Properties.Error);
context.Response.ErrorDescription = GetProperty(properties, Properties.ErrorDescription);
context.Response.ErrorUri = GetProperty(properties, Properties.ErrorUri);
context.Response.Scope = GetProperty(properties, Properties.Scope);
}
return default;
static string? GetProperty(AuthenticationProperties properties, string name)
=> properties.Dictionary.TryGetValue(name, out string? value) ? value : null;
}
}
/// <summary>
/// Contains the logic responsible for resolving the additional challenge parameters stored in the
/// OWIN authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictServerHandler<ProcessChallengeContext> public class ResolveHostChallengeProperties : IOpenIddictServerHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -330,8 +288,8 @@ public static partial class OpenIddictServerOwinHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<ResolveHostChallengeProperties>()
.SetOrder(AttachCustomChallengeParameters.Descriptor.Order - 500) .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -344,12 +302,12 @@ public static partial class OpenIddictServerOwinHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not { Dictionary.Count: > 0 })
{ {
return default; return default;
} }
// Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed // Note: unlike ASP.NET Core, OWIN's AuthenticationProperties doesn't offer a strongly-typed
// dictionary that allows flowing parameters while preserving their original types. To allow // dictionary that allows flowing parameters while preserving their original types. To allow
// returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary
// but requires suffixing the properties that are meant to be used as parameters using a special // but requires suffixing the properties that are meant to be used as parameters using a special
@ -385,18 +343,64 @@ public static partial class OpenIddictServerOwinHandlers
{ {
context.Parameters[name] = value; context.Parameters[name] = value;
} }
else
{
context.Properties[property.Key] = property.Value;
}
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching the error details using the OWIN authentication properties.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class AttachHostChallengeError : IOpenIddictServerHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is not null)
{
context.Response.Error = GetProperty(properties, Properties.Error);
context.Response.ErrorDescription = GetProperty(properties, Properties.ErrorDescription);
context.Response.ErrorUri = GetProperty(properties, Properties.ErrorUri);
context.Response.Scope = GetProperty(properties, Properties.Scope);
} }
return default; return default;
static string? GetProperty(AuthenticationProperties properties, string name)
=> properties.Dictionary.TryGetValue(name, out string? value) ? value : null;
} }
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-in parameters stored in the /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// OWIN authentication properties specified by the application that triggered the sign-in operation. /// OWIN authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ResolveHostSignInParameters : IOpenIddictServerHandler<ProcessSignInContext> public class ResolveHostSignInProperties : IOpenIddictServerHandler<ProcessSignInContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -404,8 +408,8 @@ public static partial class OpenIddictServerOwinHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostSignInParameters>() .UseSingletonHandler<ResolveHostSignInProperties>()
.SetOrder(AttachCustomSignInParameters.Descriptor.Order - 500) .SetOrder(ValidateSignInDemand.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -417,21 +421,13 @@ public static partial class OpenIddictServerOwinHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not { Dictionary.Count: > 0 })
{ {
return default; return default;
} }
// Preserve the host properties in the principal. // Note: unlike ASP.NET Core, OWIN's AuthenticationProperties doesn't offer a strongly-typed
if (properties.Dictionary.Count is not 0)
{
context.Principal.SetClaim(Claims.Private.HostProperties, properties.Dictionary);
}
// Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed
// dictionary that allows flowing parameters while preserving their original types. To allow // dictionary that allows flowing parameters while preserving their original types. To allow
// returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary
// but requires suffixing the properties that are meant to be used as parameters using a special // but requires suffixing the properties that are meant to be used as parameters using a special
@ -467,6 +463,11 @@ public static partial class OpenIddictServerOwinHandlers
{ {
context.Parameters[name] = value; context.Parameters[name] = value;
} }
else
{
context.Properties[property.Key] = property.Value;
}
} }
return default; return default;
@ -474,11 +475,11 @@ public static partial class OpenIddictServerOwinHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-out parameters stored in the /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// OWIN authentication properties specified by the application that triggered the sign-out operation. /// OWIN authentication properties specified by the application that triggered the sign-out operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ResolveHostSignOutParameters : IOpenIddictServerHandler<ProcessSignOutContext> public class ResolveHostSignOutProperties : IOpenIddictServerHandler<ProcessSignOutContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -486,8 +487,8 @@ public static partial class OpenIddictServerOwinHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignOutContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostSignOutParameters>() .UseSingletonHandler<ResolveHostSignOutProperties>()
.SetOrder(AttachCustomSignOutParameters.Descriptor.Order - 500) .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -500,12 +501,12 @@ public static partial class OpenIddictServerOwinHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not { Dictionary.Count: > 0 })
{ {
return default; return default;
} }
// Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed // Note: unlike ASP.NET Core, OWIN's AuthenticationProperties doesn't offer a strongly-typed
// dictionary that allows flowing parameters while preserving their original types. To allow // dictionary that allows flowing parameters while preserving their original types. To allow
// returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary
// but requires suffixing the properties that are meant to be used as parameters using a special // but requires suffixing the properties that are meant to be used as parameters using a special
@ -541,6 +542,11 @@ public static partial class OpenIddictServerOwinHandlers
{ {
context.Parameters[name] = value; context.Parameters[name] = value;
} }
else
{
context.Properties[property.Key] = property.Value;
}
} }
return default; return default;

21
src/OpenIddict.Server/OpenIddictServerEvents.cs

@ -565,7 +565,12 @@ public static partial class OpenIddictServerEvents
} }
/// <summary> /// <summary>
/// Gets the additional parameters returned to caller. /// Gets the user-defined authentication properties, if available.
/// </summary>
public Dictionary<string, string?> Properties { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>
public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal); public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal);
} }
@ -602,7 +607,12 @@ public static partial class OpenIddictServerEvents
} }
/// <summary> /// <summary>
/// Gets the additional parameters returned to caller. /// Gets the user-defined authentication properties, if available.
/// </summary>
public Dictionary<string, string?> Properties { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>
public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal); public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal);
@ -813,7 +823,12 @@ public static partial class OpenIddictServerEvents
} }
/// <summary> /// <summary>
/// Gets the additional parameters returned to caller. /// Gets the user-defined authentication properties, if available.
/// </summary>
public Dictionary<string, string?> Properties { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>
public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal); public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal);
} }

34
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -49,6 +49,7 @@ public static partial class OpenIddictServerHandlers
ValidateSignInDemand.Descriptor, ValidateSignInDemand.Descriptor,
RedeemTokenEntry.Descriptor, RedeemTokenEntry.Descriptor,
RestoreInternalClaims.Descriptor, RestoreInternalClaims.Descriptor,
AttachHostProperties.Descriptor,
AttachDefaultScopes.Descriptor, AttachDefaultScopes.Descriptor,
AttachDefaultPresenters.Descriptor, AttachDefaultPresenters.Descriptor,
InferResources.Descriptor, InferResources.Descriptor,
@ -1382,6 +1383,37 @@ public static partial class OpenIddictServerHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for attaching the user-defined properties to the authentication principal.
/// </summary>
public class AttachHostProperties : IOpenIddictServerHandler<ProcessSignInContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.UseSingletonHandler<AttachHostProperties>()
.SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
context.Principal.SetClaim(Claims.Private.HostProperties, context.Properties);
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching default scopes to the authentication principal. /// Contains the logic responsible for attaching default scopes to the authentication principal.
/// </summary> /// </summary>
@ -1393,7 +1425,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.UseSingletonHandler<AttachDefaultScopes>() .UseSingletonHandler<AttachDefaultScopes>()
.SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000) .SetOrder(AttachHostProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();

79
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs

@ -8,7 +8,6 @@ using System.Collections.Immutable;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -42,8 +41,8 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeProperties.Descriptor,
AttachHostChallengeError.Descriptor, AttachHostChallengeError.Descriptor,
ResolveHostChallengeParameters.Descriptor,
/* /*
* Response processing: * Response processing:
@ -279,10 +278,11 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties. /// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// ASP.NET Core authentication properties specified by the application that triggered the challenge operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class AttachHostChallengeError : IOpenIddictValidationHandler<ProcessChallengeContext> public class ResolveHostChallengeProperties : IOpenIddictValidationHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -290,8 +290,8 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<AttachHostChallengeError>() .UseSingletonHandler<ResolveHostChallengeProperties>()
.SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) .SetOrder(AttachHostChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -304,12 +304,34 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is not null) if (properties is { Items.Count: > 0 })
{ {
context.Response.Error = properties.GetString(Properties.Error); foreach (var property in properties.Items)
context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription); {
context.Response.ErrorUri = properties.GetString(Properties.ErrorUri); context.Properties[property.Key] = property.Value;
context.Response.Scope = properties.GetString(Properties.Scope); }
}
if (properties is { Parameters.Count: > 0 })
{
foreach (var parameter in properties.Parameters)
{
context.Parameters[parameter.Key] = parameter.Value switch
{
OpenIddictParameter value => value,
JsonElement value => new OpenIddictParameter(value),
bool value => new OpenIddictParameter(value),
int value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}
} }
return default; return default;
@ -317,11 +339,10 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET /// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties.
/// Core authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictValidationHandler<ProcessChallengeContext> public class AttachHostChallengeError : IOpenIddictValidationHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -329,8 +350,8 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(AttachCustomChallengeParameters.Descriptor.Order - 500) .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -343,28 +364,12 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not null)
{
return default;
}
foreach (var parameter in properties.Parameters)
{ {
context.Parameters[parameter.Key] = parameter.Value switch context.Response.Error = properties.GetString(Properties.Error);
{ context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription);
OpenIddictParameter value => value, context.Response.ErrorUri = properties.GetString(Properties.ErrorUri);
JsonElement value => new OpenIddictParameter(value), context.Response.Scope = properties.GetString(Properties.Scope);
bool value => new OpenIddictParameter(value),
int value => new OpenIddictParameter(value),
long value => new OpenIddictParameter(value),
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
} }
return default; return default;

8
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs

@ -36,4 +36,12 @@ public static class OpenIddictValidationOwinConstants
public const string ErrorUri = ".error_uri"; public const string ErrorUri = ".error_uri";
public const string Scope = ".scope"; public const string Scope = ".scope";
} }
public static class PropertyTypes
{
public const string Boolean = "#boolean";
public const string Integer = "#integer";
public const string Json = "#json";
public const string String = "#string";
}
} }

82
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs

@ -7,7 +7,9 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.Text; using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Owin; using Owin;
@ -35,6 +37,7 @@ public static partial class OpenIddictValidationOwinHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeProperties.Descriptor,
AttachHostChallengeError.Descriptor, AttachHostChallengeError.Descriptor,
/* /*
@ -276,6 +279,85 @@ public static partial class OpenIddictValidationOwinHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for resolving the context-specific properties and parameters stored in the
/// OWIN authentication properties specified by the application that triggered the challenge operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class ResolveHostChallengeProperties : IOpenIddictValidationHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostChallengeProperties>()
.SetOrder(AttachHostChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is not { Dictionary.Count: > 0 })
{
return default;
}
// Note: unlike ASP.NET Core, OWIN's AuthenticationProperties doesn't offer a strongly-typed
// dictionary that allows flowing parameters while preserving their original types. To allow
// returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary
// but requires suffixing the properties that are meant to be used as parameters using a special
// suffix that indicates that the property is public and determines its actual representation.
foreach (var property in properties.Dictionary)
{
var (name, value) = property.Key switch
{
// If the property ends with #string, represent it as a string parameter.
string key when key.EndsWith(PropertyTypes.String, StringComparison.OrdinalIgnoreCase) => (
Name: key.Substring(0, key.Length - PropertyTypes.String.Length),
Value: new OpenIddictParameter(property.Value)),
// If the property ends with #boolean, return it as a boolean parameter.
string key when key.EndsWith(PropertyTypes.Boolean, StringComparison.OrdinalIgnoreCase) => (
Name: key.Substring(0, key.Length - PropertyTypes.Boolean.Length),
Value: new OpenIddictParameter(bool.Parse(property.Value))),
// If the property ends with #integer, return it as an integer parameter.
string key when key.EndsWith(PropertyTypes.Integer, StringComparison.OrdinalIgnoreCase) => (
Name: key.Substring(0, key.Length - PropertyTypes.Integer.Length),
Value: new OpenIddictParameter(long.Parse(property.Value, CultureInfo.InvariantCulture))),
// If the property ends with #json, return it as a JSON parameter.
string key when key.EndsWith(PropertyTypes.Json, StringComparison.OrdinalIgnoreCase) => (
Name: key.Substring(0, key.Length - PropertyTypes.Json.Length),
Value: new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>(property.Value))),
_ => default
};
if (!string.IsNullOrEmpty(name))
{
context.Parameters[name] = value;
}
else
{
context.Properties[property.Key] = property.Value;
}
}
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the error details using the OWIN authentication properties. /// Contains the logic responsible for attaching the error details using the OWIN authentication properties.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.

7
src/OpenIddict.Validation/OpenIddictValidationEvents.cs

@ -331,7 +331,12 @@ public static partial class OpenIddictValidationEvents
} }
/// <summary> /// <summary>
/// Gets the additional parameters returned to caller. /// Gets the user-defined authentication properties, if available.
/// </summary>
public Dictionary<string, string?> Properties { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>
public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal); public Dictionary<string, OpenIddictParameter> Parameters { get; } = new(StringComparer.Ordinal);
} }

135
test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs

@ -136,6 +136,48 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc);
} }
[Fact]
public async Task ProcessChallenge_ImportsAuthenticationProperties()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/challenge/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessChallengeContext>(builder =>
builder.UseInlineHandler(context =>
{
Assert.Equal("value", context.Properties["custom_property"]);
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.NotEmpty(response.Error);
Assert.NotEmpty(response.ErrorDescription);
Assert.NotEmpty(response.ErrorUri);
}
[Fact] [Fact]
public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties()
{ {
@ -734,6 +776,46 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
Assert.Equal("Bob le Magnifique", (string?) response["name"]); Assert.Equal("Bob le Magnifique", (string?) response["name"]);
} }
[Fact]
public async Task ProcessSignIn_ImportsAuthenticationProperties()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/signin/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessSignInContext>(builder =>
builder.UseInlineHandler(context =>
{
Assert.Equal("value", context.Properties["custom_property"]);
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/signin/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.NotEmpty(response.AccessToken);
}
[Fact] [Fact]
public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties() public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties()
{ {
@ -782,6 +864,45 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
#endif #endif
} }
[Fact]
public async Task ProcessSignOut_ImportsAuthenticationProperties()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetLogoutEndpointUris("/signout/custom");
options.AddEventHandler<HandleLogoutRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessSignOutContext>(builder =>
builder.UseInlineHandler(context =>
{
Assert.Equal("value", context.Properties["custom_property"]);
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/signout/custom", new OpenIddictRequest
{
PostLogoutRedirectUri = "http://www.fabrikam.com/path",
State = "af0ifjsldkj"
});
// Assert
Assert.NotEmpty(response.State);
}
[Fact] [Fact]
public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties() public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties()
{ {
@ -904,7 +1025,10 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
var principal = new ClaimsPrincipal(identity); var principal = new ClaimsPrincipal(identity);
var properties = new AuthenticationProperties( var properties = new AuthenticationProperties(
items: new Dictionary<string, string?>(), items: new Dictionary<string, string?>
{
["custom_property"] = "value"
},
parameters: new Dictionary<string, object?> parameters: new Dictionary<string, object?>
{ {
["boolean_parameter"] = true, ["boolean_parameter"] = true,
@ -931,7 +1055,10 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
else if (context.Request.Path == "/signout/custom") else if (context.Request.Path == "/signout/custom")
{ {
var properties = new AuthenticationProperties( var properties = new AuthenticationProperties(
items: new Dictionary<string, string?>(), items: new Dictionary<string, string?>
{
["custom_property"] = "value"
},
parameters: new Dictionary<string, object?> parameters: new Dictionary<string, object?>
{ {
["boolean_parameter"] = true, ["boolean_parameter"] = true,
@ -956,7 +1083,9 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
{ {
[OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error", [OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error",
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description", [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description",
[OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri" [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri",
["custom_property"] = "value"
}, },
parameters: new Dictionary<string, object?> parameters: new Dictionary<string, object?>
{ {

127
test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

@ -130,6 +130,48 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte
Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc);
} }
[Fact]
public async Task ProcessChallenge_ImportsAuthenticationProperties()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/challenge/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessChallengeContext>(builder =>
builder.UseInlineHandler(context =>
{
Assert.Equal("value", context.Properties["custom_property"]);
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.NotEmpty(response.Error);
Assert.NotEmpty(response.ErrorDescription);
Assert.NotEmpty(response.ErrorUri);
}
[Fact] [Fact]
public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties()
{ {
@ -719,6 +761,46 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte
Assert.Equal("Bob le Magnifique", (string?) response["name"]); Assert.Equal("Bob le Magnifique", (string?) response["name"]);
} }
[Fact]
public async Task ProcessSignIn_ImportsAuthenticationProperties()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/signin/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessSignInContext>(builder =>
builder.UseInlineHandler(context =>
{
Assert.Equal("value", context.Properties["custom_property"]);
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/signin/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.NotEmpty(response.AccessToken);
}
[Fact] [Fact]
public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties() public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties()
{ {
@ -758,6 +840,45 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte
Assert.Equal(JsonValueKind.Array, ((JsonElement) response["json_parameter"]).ValueKind); Assert.Equal(JsonValueKind.Array, ((JsonElement) response["json_parameter"]).ValueKind);
} }
[Fact]
public async Task ProcessSignOut_ImportsAuthenticationProperties()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetLogoutEndpointUris("/signout/custom");
options.AddEventHandler<HandleLogoutRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessSignOutContext>(builder =>
builder.UseInlineHandler(context =>
{
Assert.Equal("value", context.Properties["custom_property"]);
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/signout/custom", new OpenIddictRequest
{
PostLogoutRedirectUri = "http://www.fabrikam.com/path",
State = "af0ifjsldkj"
});
// Assert
Assert.NotEmpty(response.State);
}
[Fact] [Fact]
public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties() public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties()
{ {
@ -866,6 +987,8 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte
var properties = new AuthenticationProperties(new Dictionary<string, string?> var properties = new AuthenticationProperties(new Dictionary<string, string?>
{ {
["custom_property"] = "value",
["boolean_parameter#boolean"] = "true", ["boolean_parameter#boolean"] = "true",
["integer_parameter#integer"] = "42", ["integer_parameter#integer"] = "42",
["string_parameter#string"] = "Bob l'Eponge", ["string_parameter#string"] = "Bob l'Eponge",
@ -887,6 +1010,8 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte
var properties = new AuthenticationProperties(new Dictionary<string, string?> var properties = new AuthenticationProperties(new Dictionary<string, string?>
{ {
["custom_property"] = "value",
["boolean_parameter#boolean"] = "true", ["boolean_parameter#boolean"] = "true",
["integer_parameter#integer"] = "42", ["integer_parameter#integer"] = "42",
["string_parameter#string"] = "Bob l'Eponge" ["string_parameter#string"] = "Bob l'Eponge"
@ -910,6 +1035,8 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = "custom_error_description", [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "custom_error_description",
[OpenIddictServerOwinConstants.Properties.ErrorUri] = "custom_error_uri", [OpenIddictServerOwinConstants.Properties.ErrorUri] = "custom_error_uri",
["custom_property"] = "value",
["boolean_parameter#boolean"] = "true", ["boolean_parameter#boolean"] = "true",
["integer_parameter#integer"] = "42", ["integer_parameter#integer"] = "42",
["string_parameter#string"] = "Bob l'Eponge", ["string_parameter#string"] = "Bob l'Eponge",

Loading…
Cancel
Save