From 43e75cd49ffe8aa57aefb8825ba7dcc14540b17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 10 Oct 2022 15:53:50 +0200 Subject: [PATCH] Update the ProcessChallenge/SignIn/SignOut events to expose the host authentication properties --- .../Controllers/AuthenticationController.cs | 2 +- .../Views/Home/Index.cshtml | 38 ++- .../Controllers/AuthenticationController.cs | 9 +- .../Views/Home/Index.cshtml | 40 ++- .../Controllers/AuthenticationController.cs | 2 +- .../OpenIddictClientAspNetCoreHandlers.cs | 232 +++++++++--------- .../OpenIddictClientOwinHandlers.cs | 56 ++--- .../OpenIddictClientEvents.cs | 14 +- .../OpenIddictClientHandlers.cs | 68 ++++- .../OpenIddictServerAspNetCoreHandlers.cs | 179 +++++++------- .../OpenIddictServerOwinHandlers.cs | 150 +++++------ .../OpenIddictServerEvents.cs | 21 +- .../OpenIddictServerHandlers.cs | 34 ++- .../OpenIddictValidationAspNetCoreHandlers.cs | 79 +++--- .../OpenIddictValidationOwinConstants.cs | 8 + .../OpenIddictValidationOwinHandlers.cs | 82 +++++++ .../OpenIddictValidationEvents.cs | 7 +- ...nIddictServerAspNetCoreIntegrationTests.cs | 135 +++++++++- .../OpenIddictServerOwinIntegrationTests.cs | 127 ++++++++++ 19 files changed, 901 insertions(+), 382 deletions(-) diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs index bdd0d2ae..10f72d17 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs @@ -15,7 +15,7 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers { public class AuthenticationController : Controller { - [HttpGet, Route("~/login")] + [HttpPost, Route("~/login"), ValidateAntiForgeryToken] public ActionResult LogIn(string provider, string returnUrl) { var context = HttpContext.GetOwinContext(); diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml index d996692f..2115a85a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml @@ -31,6 +31,8 @@
@Html.AntiForgeryToken() + +
} @@ -38,15 +40,31 @@ else {

Welcome, anonymous

- @Html.ActionLink("Sign in using the local OIDC server", "Login", "Authentication", - new { provider = "Local" }, new { @class = "btn btn-lg btn-success" }) - @Html.ActionLink("Sign in using the local OIDC server (using GitHub delegation)", "Login", "Authentication", - new { provider = "Local+GitHub" }, new { @class = "btn btn-lg btn-success" }) - @Html.ActionLink("Sign in using GitHub", "Login", "Authentication", - new { provider = "GitHub" }, new { @class = "btn btn-lg btn-success" }) - @Html.ActionLink("Sign in using Google", "Login", "Authentication", - new { provider = "Google" }, new { @class = "btn btn-lg btn-success" }) - @Html.ActionLink("Sign in using Twitter", "Login", "Authentication", - new { provider = "Twitter" }, new { @class = "btn btn-lg btn-success" }) + +
+ @Html.AntiForgeryToken() + + + + + + + + + + + + +
} \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs index 6ce2bc4b..7e33c2ba 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs @@ -10,7 +10,7 @@ namespace OpenIddict.Sandbox.AspNetCore.Client.Controllers; public class AuthenticationController : Controller { - [HttpGet("~/login")] + [HttpPost("~/login"), ValidateAntiForgeryToken] public ActionResult LogIn(string provider, string returnUrl) { // 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. 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. @@ -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 // 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. - 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."); } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml index d5393c95..4c97a1d7 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml @@ -33,17 +33,33 @@ else {

Welcome, anonymous

- Sign in using the local OIDC server - Sign in using the local OIDC server (using GitHub delegation) - Sign in using GitHub - Sign in using Google - Sign in using Reddit - Sign in using Twitter + +
+ + + + + + + + + + + + + +
} \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs index 21d4ef21..419185cb 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs +++ b/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 // 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. - 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."); } diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs index 5d0a410c..9729d3ee 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs @@ -42,13 +42,13 @@ public static partial class OpenIddictClientAspNetCoreHandlers /* * Challenge processing: */ - ResolveHostChallengeParameters.Descriptor, + ResolveHostChallengeProperties.Descriptor, GenerateLoginCorrelationCookie.Descriptor, /* * Sign-out processing: */ - ResolveHostSignOutParameters.Descriptor, + ResolveHostSignOutProperties.Descriptor, GenerateLogoutCorrelationCookie.Descriptor) .AddRange(Authentication.DefaultHandlers) .AddRange(Session.DefaultHandlers); @@ -414,11 +414,11 @@ public static partial class OpenIddictClientAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the challenge operation. + /// 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. /// - public class ResolveHostChallengeParameters : IOpenIddictClientHandler + public class ResolveHostChallengeProperties : IOpenIddictClientHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -426,7 +426,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() + .UseSingletonHandler() .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -439,76 +439,74 @@ public static partial class OpenIddictClientAspNetCoreHandlers throw new ArgumentNullException(nameof(context)); } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - 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)) + if (properties is { Items.Count: > 0 }) { - // Ensure the issuer set by the application is a valid absolute URI. - if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) + // 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)) { - 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 (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) && - !string.IsNullOrEmpty(provider)) - { - context.ProviderName = provider; - } + // If a provider name was explicitly set, update the challenge context to use it. + if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) && + !string.IsNullOrEmpty(provider)) + { + context.ProviderName = provider; + } - // If a return URL was specified, use it as the target_link_uri claim. - if (!string.IsNullOrEmpty(properties.RedirectUri)) - { - context.TargetLinkUri = properties.RedirectUri; - } + // If a return URL was specified, use it as the target_link_uri claim. + if (!string.IsNullOrEmpty(properties.RedirectUri)) + { + context.TargetLinkUri = properties.RedirectUri; + } - // If an identity token hint was specified, attach it to the context. - if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) && - !string.IsNullOrEmpty(token)) - { - context.IdentityTokenHint = token; - } + // If an identity token hint was specified, attach it to the context. + if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) && + !string.IsNullOrEmpty(token)) + { + context.IdentityTokenHint = token; + } - // If a login hint was specified, attach it to the context. - if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) && - !string.IsNullOrEmpty(hint)) - { - context.LoginHint = hint; - } + // If a login hint was specified, attach it to the context. + if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) && + !string.IsNullOrEmpty(hint)) + { + context.LoginHint = hint; + } - // Preserve the host properties in the principal. - if (properties.Items.Count is not 0) - { - context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); + 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, - 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), + 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), + JsonNode value => new OpenIddictParameter(value), #endif - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; + } } return default; @@ -590,11 +588,11 @@ public static partial class OpenIddictClientAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional sign-out parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-out operation. + /// 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 sign-out operation. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// - public class ResolveHostSignOutParameters : IOpenIddictClientHandler + public class ResolveHostSignOutProperties : IOpenIddictClientHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -602,7 +600,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() + .UseSingletonHandler() .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -615,76 +613,74 @@ public static partial class OpenIddictClientAspNetCoreHandlers throw new ArgumentNullException(nameof(context)); } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - 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)) + if (properties is { Items.Count: > 0 }) { - // Ensure the issuer set by the application is a valid absolute URI. - if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) + // 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)) { - 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 (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) && - !string.IsNullOrEmpty(provider)) - { - context.ProviderName = provider; - } + // If a provider name was explicitly set, update the sign-out context to use it. + if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) && + !string.IsNullOrEmpty(provider)) + { + context.ProviderName = provider; + } - // If a return URL was specified, use it as the target_link_uri claim. - if (!string.IsNullOrEmpty(properties.RedirectUri)) - { - context.TargetLinkUri = properties.RedirectUri; - } + // If a return URL was specified, use it as the target_link_uri claim. + if (!string.IsNullOrEmpty(properties.RedirectUri)) + { + context.TargetLinkUri = properties.RedirectUri; + } - // If an identity token hint was specified, attach it to the context. - if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) && - !string.IsNullOrEmpty(token)) - { - context.IdentityTokenHint = token; - } + // If an identity token hint was specified, attach it to the context. + if (properties.Items.TryGetValue(Properties.IdentityTokenHint, out string? token) && + !string.IsNullOrEmpty(token)) + { + context.IdentityTokenHint = token; + } - // If a login hint was specified, attach it to the context. - if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) && - !string.IsNullOrEmpty(hint)) - { - context.LoginHint = hint; - } + // If a login hint was specified, attach it to the context. + if (properties.Items.TryGetValue(Properties.LoginHint, out string? hint) && + !string.IsNullOrEmpty(hint)) + { + context.LoginHint = hint; + } - // Preserve the host properties in the principal. - if (properties.Items.Count is not 0) - { - context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); + 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, - 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), + 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), + JsonNode value => new OpenIddictParameter(value), #endif - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; + } } return default; diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs index 16c8e197..4ece2246 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs @@ -38,13 +38,13 @@ public static partial class OpenIddictClientOwinHandlers /* * Challenge processing: */ - ResolveHostChallengeParameters.Descriptor, + ResolveHostChallengeProperties.Descriptor, GenerateLoginCorrelationCookie.Descriptor, /* * Sign-out processing: */ - ResolveHostSignOutParameters.Descriptor, + ResolveHostSignOutProperties.Descriptor, GenerateLogoutCorrelationCookie.Descriptor) .AddRange(Authentication.DefaultHandlers) .AddRange(Session.DefaultHandlers); @@ -319,7 +319,7 @@ public static partial class OpenIddictClientOwinHandlers /// /// 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. /// public class ValidateEndpointUri : IOpenIddictClientHandler { @@ -425,11 +425,11 @@ public static partial class OpenIddictClientOwinHandlers } /// - /// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the challenge operation. + /// 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. /// - public class ResolveHostChallengeParameters : IOpenIddictClientHandler + public class ResolveHostChallengeProperties : IOpenIddictClientHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -437,7 +437,7 @@ public static partial class OpenIddictClientOwinHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() + .UseSingletonHandler() .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -450,10 +450,8 @@ public static partial class OpenIddictClientOwinHandlers throw new ArgumentNullException(nameof(context)); } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) + if (properties is not { Dictionary.Count: > 0 }) { return default; } @@ -497,13 +495,7 @@ public static partial class OpenIddictClientOwinHandlers context.LoginHint = hint; } - // Preserve the host properties in the principal. - 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 + // 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 @@ -539,6 +531,11 @@ public static partial class OpenIddictClientOwinHandlers { context.Parameters[name] = value; } + + else + { + context.Properties[property.Key] = property.Value; + } } return default; @@ -628,11 +625,11 @@ public static partial class OpenIddictClientOwinHandlers } /// - /// Contains the logic responsible for resolving the additional sign-out parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-out operation. + /// 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. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// - public class ResolveHostSignOutParameters : IOpenIddictClientHandler + public class ResolveHostSignOutProperties : IOpenIddictClientHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -640,7 +637,7 @@ public static partial class OpenIddictClientOwinHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() + .UseSingletonHandler() .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -653,10 +650,8 @@ public static partial class OpenIddictClientOwinHandlers throw new ArgumentNullException(nameof(context)); } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) + if (properties is not { Dictionary.Count: > 0 }) { return default; } @@ -700,13 +695,7 @@ public static partial class OpenIddictClientOwinHandlers context.LoginHint = hint; } - // Preserve the host properties in the principal. - 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 + // 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 @@ -742,6 +731,11 @@ public static partial class OpenIddictClientOwinHandlers { context.Parameters[name] = value; } + + else + { + context.Properties[property.Key] = property.Value; + } } return default; diff --git a/src/OpenIddict.Client/OpenIddictClientEvents.cs b/src/OpenIddict.Client/OpenIddictClientEvents.cs index ed7cdba2..2e7672d9 100644 --- a/src/OpenIddict.Client/OpenIddictClientEvents.cs +++ b/src/OpenIddict.Client/OpenIddictClientEvents.cs @@ -693,6 +693,11 @@ public static partial class OpenIddictClientEvents set => Transaction.Response = value; } + /// + /// Gets the user-defined authentication properties, if available. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + /// /// Gets or sets the name of the provider that will be /// used to resolve the issuer identity, if applicable. @@ -700,7 +705,7 @@ public static partial class OpenIddictClientEvents public string? ProviderName { get; set; } /// - /// Gets the additional parameters returned to caller. + /// Gets the additional parameters returned to the caller. /// public Dictionary Parameters { get; } = new(StringComparer.Ordinal); @@ -843,6 +848,11 @@ public static partial class OpenIddictClientEvents set => Transaction.Response = value; } + /// + /// Gets the user-defined authentication properties, if available. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + /// /// Gets or sets the name of the provider that will be /// used to resolve the issuer identity, if applicable. @@ -884,7 +894,7 @@ public static partial class OpenIddictClientEvents public string? RequestForgeryProtection { get; set; } /// - /// Gets the additional parameters returned to caller. + /// Gets the additional parameters returned to the caller. /// public Dictionary Parameters { get; } = new(StringComparer.Ordinal); diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index 2c1f01c5..ecb8ccde 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -94,6 +94,7 @@ public static partial class OpenIddictClientHandlers ResolveClientRegistrationFromChallengeContext.Descriptor, AttachGrantType.Descriptor, EvaluateGeneratedChallengeTokens.Descriptor, + AttachChallengeHostProperties.Descriptor, AttachResponseType.Descriptor, AttachResponseMode.Descriptor, AttachClientId.Descriptor, @@ -116,6 +117,7 @@ public static partial class OpenIddictClientHandlers AttachOptionalClientId.Descriptor, AttachPostLogoutRedirectUri.Descriptor, EvaluateGeneratedLogoutTokens.Descriptor, + AttachSignOutHostProperties.Descriptor, AttachLogoutRequestForgeryProtection.Descriptor, PrepareLogoutStateTokenPrincipal.Descriptor, GenerateLogoutStateToken.Descriptor, @@ -3567,6 +3569,37 @@ public static partial class OpenIddictClientHandlers } } + /// + /// Contains the logic responsible for attaching the user-defined properties to the authentication principal. + /// + public class AttachChallengeHostProperties : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(EvaluateGeneratedChallengeTokens.Descriptor.Order + 1_000) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + 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; + } + } + /// /// Contains the logic responsible for attaching the response type to the challenge request. /// @@ -3579,7 +3612,7 @@ public static partial class OpenIddictClientHandlers = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(EvaluateGeneratedChallengeTokens.Descriptor.Order + 1_000) + .SetOrder(AttachChallengeHostProperties.Descriptor.Order + 1_000) .Build(); /// @@ -4646,6 +4679,37 @@ public static partial class OpenIddictClientHandlers } } + /// + /// Contains the logic responsible for attaching the user-defined properties to the authentication principal. + /// + public class AttachSignOutHostProperties : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(EvaluateGeneratedLogoutTokens.Descriptor.Order + 1_000) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + 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; + } + } + /// /// Contains the logic responsible for attaching a request forgery protection to the authorization request. /// @@ -4657,7 +4721,7 @@ public static partial class OpenIddictClientHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .UseSingletonHandler() - .SetOrder(EvaluateGeneratedLogoutTokens.Descriptor.Order + 1_000) + .SetOrder(AttachSignOutHostProperties.Descriptor.Order + 1_000) .Build(); /// diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index e258ac1b..6a62dd43 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; -using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -38,18 +37,18 @@ public static partial class OpenIddictServerAspNetCoreHandlers /* * Challenge processing: */ + ResolveHostChallengeProperties.Descriptor, AttachHostChallengeError.Descriptor, - ResolveHostChallengeParameters.Descriptor, /* * Sign-in processing: */ - ResolveHostSignInParameters.Descriptor, + ResolveHostSignInProperties.Descriptor, /* * Sign-out processing: */ - ResolveHostSignOutParameters.Descriptor) + ResolveHostSignOutProperties.Descriptor) .AddRange(Authentication.DefaultHandlers) .AddRange(Device.DefaultHandlers) .AddRange(Discovery.DefaultHandlers) @@ -279,10 +278,11 @@ public static partial class OpenIddictServerAspNetCoreHandlers } /// - /// 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. /// - public class AttachHostChallengeError : IOpenIddictServerHandler + public class ResolveHostChallengeProperties : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -290,8 +290,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -304,12 +304,34 @@ public static partial class OpenIddictServerAspNetCoreHandlers } var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is not null) + if (properties is { Items.Count: > 0 }) { - context.Response.Error = properties.GetString(Properties.Error); - context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription); - context.Response.ErrorUri = properties.GetString(Properties.ErrorUri); - context.Response.Scope = properties.GetString(Properties.Scope); + foreach (var property in properties.Items) + { + context.Properties[property.Key] = property.Value; + } + } + + 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; @@ -317,11 +339,10 @@ public static partial class OpenIddictServerAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-in operation. + /// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// - public class ResolveHostChallengeParameters : IOpenIddictServerHandler + public class AttachHostChallengeError : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -329,8 +350,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomChallengeParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -343,28 +364,12 @@ public static partial class OpenIddictServerAspNetCoreHandlers } var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } - - foreach (var parameter in properties.Parameters) + if (properties is not null) { - 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)) - }; + context.Response.Error = properties.GetString(Properties.Error); + context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription); + context.Response.ErrorUri = properties.GetString(Properties.ErrorUri); + context.Response.Scope = properties.GetString(Properties.Scope); } return default; @@ -372,11 +377,11 @@ public static partial class OpenIddictServerAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-in operation. + /// 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 sign-in operation. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// - public class ResolveHostSignInParameters : IOpenIddictServerHandler + public class ResolveHostSignInProperties : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -384,8 +389,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomSignInParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(ValidateSignInDemand.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -397,37 +402,35 @@ public static partial class OpenIddictServerAspNetCoreHandlers throw new ArgumentNullException(nameof(context)); } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) + if (properties is { Items.Count: > 0 }) { - return default; - } - - // Preserve the host properties in the principal. - if (properties.Items.Count is not 0) - { - context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); + 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, - 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), + 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), + JsonNode value => new OpenIddictParameter(value), #endif - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; + } } return default; @@ -435,11 +438,11 @@ public static partial class OpenIddictServerAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional sign-out parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-out operation. + /// 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 sign-out operation. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// - public class ResolveHostSignOutParameters : IOpenIddictServerHandler + public class ResolveHostSignOutProperties : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -447,8 +450,8 @@ public static partial class OpenIddictServerAspNetCoreHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomSignOutParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -461,28 +464,34 @@ public static partial class OpenIddictServerAspNetCoreHandlers } var properties = context.Transaction.GetProperty(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, - 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), + 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), + JsonNode value => new OpenIddictParameter(value), #endif - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; + } } return default; diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index c10ce5f2..56b046e0 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs @@ -8,7 +8,6 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Globalization; -using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -34,18 +33,18 @@ public static partial class OpenIddictServerOwinHandlers /* * Challenge processing: */ + ResolveHostChallengeProperties.Descriptor, AttachHostChallengeError.Descriptor, - ResolveHostChallengeParameters.Descriptor, /* * Sign-in processing: */ - ResolveHostSignInParameters.Descriptor, + ResolveHostSignInProperties.Descriptor, /* * Sign-out processing: */ - ResolveHostSignOutParameters.Descriptor) + ResolveHostSignOutProperties.Descriptor) .AddRange(Authentication.DefaultHandlers) .AddRange(Device.DefaultHandlers) .AddRange(Discovery.DefaultHandlers) @@ -277,52 +276,11 @@ public static partial class OpenIddictServerOwinHandlers } /// - /// 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. - /// - public class AttachHostChallengeError : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var properties = context.Transaction.GetProperty(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; - } - } - - /// - /// 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. + /// 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. /// - public class ResolveHostChallengeParameters : IOpenIddictServerHandler + public class ResolveHostChallengeProperties : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -330,8 +288,8 @@ public static partial class OpenIddictServerOwinHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomChallengeParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(ValidateChallengeDemand.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -344,12 +302,12 @@ public static partial class OpenIddictServerOwinHandlers } var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) + if (properties is not { Dictionary.Count: > 0 }) { 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 // 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 @@ -385,18 +343,64 @@ public static partial class OpenIddictServerOwinHandlers { context.Parameters[name] = value; } + + else + { + context.Properties[property.Key] = property.Value; + } + } + + return default; + } + } + + /// + /// 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. + /// + public class AttachHostChallengeError : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var properties = context.Transaction.GetProperty(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; } } /// - /// 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. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// - public class ResolveHostSignInParameters : IOpenIddictServerHandler + public class ResolveHostSignInProperties : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -404,8 +408,8 @@ public static partial class OpenIddictServerOwinHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomSignInParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(ValidateSignInDemand.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -417,21 +421,13 @@ public static partial class OpenIddictServerOwinHandlers throw new ArgumentNullException(nameof(context)); } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) + if (properties is not { Dictionary.Count: > 0 }) { return default; } - // Preserve the host properties in the principal. - 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 + // 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 @@ -467,6 +463,11 @@ public static partial class OpenIddictServerOwinHandlers { context.Parameters[name] = value; } + + else + { + context.Properties[property.Key] = property.Value; + } } return default; @@ -474,11 +475,11 @@ public static partial class OpenIddictServerOwinHandlers } /// - /// 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. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// - public class ResolveHostSignOutParameters : IOpenIddictServerHandler + public class ResolveHostSignOutProperties : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -486,8 +487,8 @@ public static partial class OpenIddictServerOwinHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomSignOutParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(ValidateSignOutDemand.Descriptor.Order - 500) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); @@ -500,12 +501,12 @@ public static partial class OpenIddictServerOwinHandlers } var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) + if (properties is not { Dictionary.Count: > 0 }) { 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 // 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 @@ -541,6 +542,11 @@ public static partial class OpenIddictServerOwinHandlers { context.Parameters[name] = value; } + + else + { + context.Properties[property.Key] = property.Value; + } } return default; diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.cs b/src/OpenIddict.Server/OpenIddictServerEvents.cs index 37c7e014..cae31091 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.cs @@ -565,7 +565,12 @@ public static partial class OpenIddictServerEvents } /// - /// Gets the additional parameters returned to caller. + /// Gets the user-defined authentication properties, if available. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the additional parameters returned to the caller. /// public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } @@ -602,7 +607,12 @@ public static partial class OpenIddictServerEvents } /// - /// Gets the additional parameters returned to caller. + /// Gets the user-defined authentication properties, if available. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the additional parameters returned to the caller. /// public Dictionary Parameters { get; } = new(StringComparer.Ordinal); @@ -813,7 +823,12 @@ public static partial class OpenIddictServerEvents } /// - /// Gets the additional parameters returned to caller. + /// Gets the user-defined authentication properties, if available. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the additional parameters returned to the caller. /// public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 436142c2..dd58baa7 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -49,6 +49,7 @@ public static partial class OpenIddictServerHandlers ValidateSignInDemand.Descriptor, RedeemTokenEntry.Descriptor, RestoreInternalClaims.Descriptor, + AttachHostProperties.Descriptor, AttachDefaultScopes.Descriptor, AttachDefaultPresenters.Descriptor, InferResources.Descriptor, @@ -1382,6 +1383,37 @@ public static partial class OpenIddictServerHandlers } } + /// + /// Contains the logic responsible for attaching the user-defined properties to the authentication principal. + /// + public class AttachHostProperties : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + 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; + } + } + /// /// Contains the logic responsible for attaching default scopes to the authentication principal. /// @@ -1393,7 +1425,7 @@ public static partial class OpenIddictServerHandlers public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() - .SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000) + .SetOrder(AttachHostProperties.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs index efed4f58..bf688229 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs @@ -8,7 +8,6 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Text; -using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore; using Microsoft.Extensions.Logging; @@ -42,8 +41,8 @@ public static partial class OpenIddictValidationAspNetCoreHandlers /* * Challenge processing: */ + ResolveHostChallengeProperties.Descriptor, AttachHostChallengeError.Descriptor, - ResolveHostChallengeParameters.Descriptor, /* * Response processing: @@ -279,10 +278,11 @@ public static partial class OpenIddictValidationAspNetCoreHandlers } /// - /// 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. /// - public class AttachHostChallengeError : IOpenIddictValidationHandler + public class ResolveHostChallengeProperties : IOpenIddictValidationHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -290,8 +290,8 @@ public static partial class OpenIddictValidationAspNetCoreHandlers public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(AttachHostChallengeError.Descriptor.Order - 500) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); @@ -304,12 +304,34 @@ public static partial class OpenIddictValidationAspNetCoreHandlers } var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is not null) + if (properties is { Items.Count: > 0 }) { - context.Response.Error = properties.GetString(Properties.Error); - context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription); - context.Response.ErrorUri = properties.GetString(Properties.ErrorUri); - context.Response.Scope = properties.GetString(Properties.Scope); + foreach (var property in properties.Items) + { + context.Properties[property.Key] = property.Value; + } + } + + 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; @@ -317,11 +339,10 @@ public static partial class OpenIddictValidationAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-in operation. + /// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// - public class ResolveHostChallengeParameters : IOpenIddictValidationHandler + public class AttachHostChallengeError : IOpenIddictValidationHandler { /// /// Gets the default descriptor definition assigned to this handler. @@ -329,8 +350,8 @@ public static partial class OpenIddictValidationAspNetCoreHandlers public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachCustomChallengeParameters.Descriptor.Order - 500) + .UseSingletonHandler() + .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); @@ -343,28 +364,12 @@ public static partial class OpenIddictValidationAspNetCoreHandlers } var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } - - foreach (var parameter in properties.Parameters) + if (properties is not null) { - 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)) - }; + context.Response.Error = properties.GetString(Properties.Error); + context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription); + context.Response.ErrorUri = properties.GetString(Properties.ErrorUri); + context.Response.Scope = properties.GetString(Properties.Scope); } return default; diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs index 27edde10..84763f60 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs @@ -36,4 +36,12 @@ public static class OpenIddictValidationOwinConstants public const string ErrorUri = ".error_uri"; 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"; + } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index 671d1310..aa44e9e3 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs @@ -7,7 +7,9 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.Text; +using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Owin; @@ -35,6 +37,7 @@ public static partial class OpenIddictValidationOwinHandlers /* * Challenge processing: */ + ResolveHostChallengeProperties.Descriptor, AttachHostChallengeError.Descriptor, /* @@ -276,6 +279,85 @@ public static partial class OpenIddictValidationOwinHandlers } } + /// + /// 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. + /// + public class ResolveHostChallengeProperties : IOpenIddictValidationHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachHostChallengeError.Descriptor.Order - 500) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var properties = context.Transaction.GetProperty(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(property.Value))), + + _ => default + }; + + if (!string.IsNullOrEmpty(name)) + { + context.Parameters[name] = value; + } + + else + { + context.Properties[property.Key] = property.Value; + } + } + + return default; + } + } + /// /// 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. diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs index d03044fd..3d3bc40d 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs @@ -331,7 +331,12 @@ public static partial class OpenIddictValidationEvents } /// - /// Gets the additional parameters returned to caller. + /// Gets the user-defined authentication properties, if available. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the additional parameters returned to the caller. /// public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs index cbbf0910..0f80c943 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs +++ b/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); } + [Fact] + public async Task ProcessChallenge_ImportsAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge/custom"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(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] public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() { @@ -734,6 +776,46 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ 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(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(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] public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties() { @@ -782,6 +864,45 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ #endif } + [Fact] + public async Task ProcessSignOut_ImportsAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/signout/custom"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(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] public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties() { @@ -904,7 +1025,10 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ var principal = new ClaimsPrincipal(identity); var properties = new AuthenticationProperties( - items: new Dictionary(), + items: new Dictionary + { + ["custom_property"] = "value" + }, parameters: new Dictionary { ["boolean_parameter"] = true, @@ -931,7 +1055,10 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ else if (context.Request.Path == "/signout/custom") { var properties = new AuthenticationProperties( - items: new Dictionary(), + items: new Dictionary + { + ["custom_property"] = "value" + }, parameters: new Dictionary { ["boolean_parameter"] = true, @@ -956,7 +1083,9 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ { [OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error", [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description", - [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri" + [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri", + + ["custom_property"] = "value" }, parameters: new Dictionary { diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs index d6bc21fc..18599e14 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs +++ b/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); } + [Fact] + public async Task ProcessChallenge_ImportsAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge/custom"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(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] public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() { @@ -719,6 +761,46 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte 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(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(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] public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties() { @@ -758,6 +840,45 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte 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(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(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] public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties() { @@ -866,6 +987,8 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte var properties = new AuthenticationProperties(new Dictionary { + ["custom_property"] = "value", + ["boolean_parameter#boolean"] = "true", ["integer_parameter#integer"] = "42", ["string_parameter#string"] = "Bob l'Eponge", @@ -887,6 +1010,8 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte var properties = new AuthenticationProperties(new Dictionary { + ["custom_property"] = "value", + ["boolean_parameter#boolean"] = "true", ["integer_parameter#integer"] = "42", ["string_parameter#string"] = "Bob l'Eponge" @@ -910,6 +1035,8 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "custom_error_description", [OpenIddictServerOwinConstants.Properties.ErrorUri] = "custom_error_uri", + ["custom_property"] = "value", + ["boolean_parameter#boolean"] = "true", ["integer_parameter#integer"] = "42", ["string_parameter#string"] = "Bob l'Eponge",