diff --git a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs index fccd11a2..2b759c36 100644 --- a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs +++ b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs @@ -5,47 +5,47 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Scriban; -namespace OpenIddict.Client.WebIntegration.Generators +namespace OpenIddict.Client.WebIntegration.Generators; + +[Generator] +public sealed class OpenIddictClientWebIntegrationGenerator : ISourceGenerator { - [Generator] - public sealed class OpenIddictClientWebIntegrationGenerator : ISourceGenerator + public void Execute(GeneratorExecutionContext context) { - public void Execute(GeneratorExecutionContext context) - { - var file = context.AdditionalFiles.Select(file => file.Path) - .Where(path => string.Equals(Path.GetFileName(path), "OpenIddictClientWebIntegrationProviders.xml")) - .SingleOrDefault(); + var file = context.AdditionalFiles.Select(file => file.Path) + .Where(path => string.Equals(Path.GetFileName(path), "OpenIddictClientWebIntegrationProviders.xml")) + .SingleOrDefault(); - if (string.IsNullOrEmpty(file)) - { - return; - } + if (string.IsNullOrEmpty(file)) + { + return; + } - var document = XDocument.Load(file, LoadOptions.None); + var document = XDocument.Load(file, LoadOptions.None); - context.AddSource( - "OpenIddictClientWebIntegrationBuilder.generated.cs", - SourceText.From(GenerateBuilderMethods(document), Encoding.UTF8)); + context.AddSource( + "OpenIddictClientWebIntegrationBuilder.generated.cs", + SourceText.From(GenerateBuilderMethods(document), Encoding.UTF8)); - context.AddSource( - "OpenIddictClientWebIntegrationConfiguration.generated.cs", - SourceText.From(GenerateConfigurationClasses(document), Encoding.UTF8)); + context.AddSource( + "OpenIddictClientWebIntegrationConfiguration.generated.cs", + SourceText.From(GenerateConfigurationClasses(document), Encoding.UTF8)); - context.AddSource( - "OpenIddictClientWebIntegrationConstants.generated.cs", - SourceText.From(GenerateConstants(document), Encoding.UTF8)); + context.AddSource( + "OpenIddictClientWebIntegrationConstants.generated.cs", + SourceText.From(GenerateConstants(document), Encoding.UTF8)); - context.AddSource( - "OpenIddictClientWebIntegrationHelpers.generated.cs", - SourceText.From(GenerateHelpers(document), Encoding.UTF8)); + context.AddSource( + "OpenIddictClientWebIntegrationHelpers.generated.cs", + SourceText.From(GenerateHelpers(document), Encoding.UTF8)); - context.AddSource( - "OpenIddictClientWebIntegrationSettings.generated.cs", - SourceText.From(GenerateSettings(document), Encoding.UTF8)); + context.AddSource( + "OpenIddictClientWebIntegrationSettings.generated.cs", + SourceText.From(GenerateSettings(document), Encoding.UTF8)); - static string GenerateBuilderMethods(XDocument document) - { - var template = Template.Parse(@"#nullable enable + static string GenerateBuilderMethods(XDocument document) + { + var template = Template.Parse(@"#nullable enable #pragma warning disable CS0618 using System.ComponentModel; @@ -743,62 +743,62 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder {{~ end ~}} } "); - return template.Render(new - { - Providers = document.Root.Elements("Provider") - .Select(provider => new - { - Name = (string) provider.Attribute("Name"), - DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), - Documentation = (string?) provider.Attribute("Documentation"), + return template.Render(new + { + Providers = document.Root.Elements("Provider") + .Select(provider => new + { + Name = (string) provider.Attribute("Name"), + DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), + Documentation = (string?) provider.Attribute("Documentation"), - Obsolete = (bool?) provider.Attribute("Obsolete") ?? false, + Obsolete = (bool?) provider.Attribute("Obsolete") ?? false, - Environments = provider.Elements("Environment").Select(environment => new - { - Name = (string?) environment.Attribute("Name") ?? "Production" - }) - .ToList(), + Environments = provider.Elements("Environment").Select(environment => new + { + Name = (string?) environment.Attribute("Name") ?? "Production" + }) + .ToList(), - Settings = provider.Elements("Setting").Select(setting => new - { - PropertyName = (string) setting.Attribute("PropertyName"), - ParameterName = (string) setting.Attribute("ParameterName"), + Settings = provider.Elements("Setting").Select(setting => new + { + PropertyName = (string) setting.Attribute("PropertyName"), + ParameterName = (string) setting.Attribute("ParameterName"), - Collection = (bool?) setting.Attribute("Collection") ?? false, - Obsolete = (bool?) setting.Attribute("Obsolete") ?? false, + Collection = (bool?) setting.Attribute("Collection") ?? false, + Obsolete = (bool?) setting.Attribute("Obsolete") ?? false, - Description = (string) setting.Attribute("Description") is string description ? - char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null, - ClrType = (string) setting.Attribute("Type") switch - { - "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value") - is "RS256" or "RS384" or "RS512" => "RsaSecurityKey", + Description = (string) setting.Attribute("Description") is string description ? + char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null, + ClrType = (string) setting.Attribute("Type") switch + { + "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value") + is "RS256" or "RS384" or "RS512" => "RsaSecurityKey", - "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") - is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey", + "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") + is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey", - "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") - is "PS256" or "PS384" or "PS512" or - "RS256" or "RS384" or "RS512" => "RsaSecurityKey", + "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") + is "PS256" or "PS384" or "PS512" or + "RS256" or "RS384" or "RS512" => "RsaSecurityKey", - "Certificate" => "X509Certificate2", - "String" => "string", - "StringHashSet" => "HashSet", - "Uri" => "Uri", + "Certificate" => "X509Certificate2", + "String" => "string", + "StringHashSet" => "HashSet", + "Uri" => "Uri", - string value => value - } - }) - .ToList() + string value => value + } }) .ToList() - }); - } + }) + .ToList() + }); + } - static string GenerateConstants(XDocument document) - { - var template = Template.Parse(@"#nullable enable + static string GenerateConstants(XDocument document) + { + var template = Template.Parse(@"#nullable enable namespace OpenIddict.Client.WebIntegration; @@ -838,34 +838,34 @@ public static partial class OpenIddictClientWebIntegrationConstants } } "); - return template.Render(new - { - Providers = document.Root.Elements("Provider") - .Select(provider => new - { - Name = (string) provider.Attribute("Name"), - Id = (string) provider.Attribute("Id"), + return template.Render(new + { + Providers = document.Root.Elements("Provider") + .Select(provider => new + { + Name = (string) provider.Attribute("Name"), + Id = (string) provider.Attribute("Id"), - Environments = provider.Elements("Environment").Select(environment => new - { - Name = (string?) environment.Attribute("Name") ?? "Production" - }) - .ToList(), + Environments = provider.Elements("Environment").Select(environment => new + { + Name = (string?) environment.Attribute("Name") ?? "Production" + }) + .ToList(), - Properties = provider.Elements("Property").Select(property => new - { - Name = (string) property.Attribute("Name"), - DictionaryKey = (string) property.Attribute("DictionaryKey") - }) - .ToList(), + Properties = provider.Elements("Property").Select(property => new + { + Name = (string) property.Attribute("Name"), + DictionaryKey = (string) property.Attribute("DictionaryKey") }) - .ToList() - }); - } + .ToList(), + }) + .ToList() + }); + } - static string GenerateConfigurationClasses(XDocument document) - { - var template = Template.Parse(@"#nullable enable + static string GenerateConfigurationClasses(XDocument document) + { + var template = Template.Parse(@"#nullable enable #pragma warning disable CS0618 using Microsoft.Extensions.DependencyInjection; @@ -1116,149 +1116,149 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration } } "); - return template.Render(new - { - Providers = document.Root.Elements("Provider") - .Select(provider => new + return template.Render(new + { + Providers = document.Root.Elements("Provider") + .Select(provider => new + { + Name = (string) provider.Attribute("Name"), + DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), + + Environments = provider.Elements("Environment").Select(environment => new { - Name = (string) provider.Attribute("Name"), - DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), + Name = (string?) environment.Attribute("Name") ?? "Production", - Environments = provider.Elements("Environment").Select(environment => new + Issuer = (string) environment.Attribute("Issuer"), + ConfigurationEndpoint = (string?) environment.Attribute("ConfigurationEndpoint"), + + Configuration = environment.Element("Configuration") switch { - Name = (string?) environment.Attribute("Name") ?? "Production", + XElement configuration => new + { + AuthorizationEndpoint = (string?) configuration.Attribute("AuthorizationEndpoint"), + DeviceAuthorizationEndpoint = (string?) configuration.Attribute("DeviceAuthorizationEndpoint"), + IntrospectionEndpoint = (string?) configuration.Attribute("IntrospectionEndpoint"), + RevocationEndpoint = (string?) configuration.Attribute("RevocationEndpoint"), + TokenEndpoint = (string?) configuration.Attribute("TokenEndpoint"), + UserinfoEndpoint = (string?) configuration.Attribute("UserinfoEndpoint"), + + CodeChallengeMethodsSupported = configuration.Elements("CodeChallengeMethod").ToList() switch + { + { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), - Issuer = (string) environment.Attribute("Issuer"), - ConfigurationEndpoint = (string?) environment.Attribute("ConfigurationEndpoint"), + _ => [] + }, - Configuration = environment.Element("Configuration") switch - { - XElement configuration => new + GrantTypesSupported = configuration.Elements("GrantType").ToList() switch { - AuthorizationEndpoint = (string?) configuration.Attribute("AuthorizationEndpoint"), - DeviceAuthorizationEndpoint = (string?) configuration.Attribute("DeviceAuthorizationEndpoint"), - IntrospectionEndpoint = (string?) configuration.Attribute("IntrospectionEndpoint"), - RevocationEndpoint = (string?) configuration.Attribute("RevocationEndpoint"), - TokenEndpoint = (string?) configuration.Attribute("TokenEndpoint"), - UserinfoEndpoint = (string?) configuration.Attribute("UserinfoEndpoint"), - - CodeChallengeMethodsSupported = configuration.Elements("CodeChallengeMethod").ToList() switch - { - { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), - - _ => [] - }, - - GrantTypesSupported = configuration.Elements("GrantType").ToList() switch - { - { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit grant type was set, assume the provider only supports the code flow. - _ => [GrantTypes.AuthorizationCode] - }, - - ResponseModesSupported = configuration.Elements("ResponseMode").ToList() switch - { - { Count: > 0 } modes => modes.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit response mode was set, assume the provider only supports the query response mode. - _ => [ResponseModes.Query] - }, - - ResponseTypesSupported = configuration.Elements("ResponseType").ToList() switch - { - { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit response type was set, assume the provider only supports the code flow. - _ => [ResponseTypes.Code] - }, - - ScopesSupported = configuration.Elements("Scope").ToList() switch - { - { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(), - - _ => [] - }, - - DeviceAuthorizationEndpointAuthMethodsSupported = configuration.Elements("DeviceAuthorizationEndpointAuthMethod").ToList() switch - { - { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit client authentication method was set, assume the provider only supports - // flowing the client credentials as part of the device authorization request payload. - _ => [ClientAuthenticationMethods.ClientSecretPost] - }, - - IntrospectionEndpointAuthMethodsSupported = configuration.Elements("IntrospectionEndpointAuthMethod").ToList() switch - { - { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit client authentication method was set, assume the provider only - // supports flowing the client credentials as part of the introspection request payload. - _ => [ClientAuthenticationMethods.ClientSecretPost] - }, - - RevocationEndpointAuthMethodsSupported = configuration.Elements("RevocationEndpointAuthMethod").ToList() switch - { - { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit client authentication method was set, assume the provider only - // supports flowing the client credentials as part of the revocation request payload. - _ => [ClientAuthenticationMethods.ClientSecretPost] - }, - - TokenEndpointAuthMethodsSupported = configuration.Elements("TokenEndpointAuthMethod").ToList() switch - { - { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), - - // If no explicit client authentication method was set, assume the provider only - // supports flowing the client credentials as part of the token request payload. - _ => [ClientAuthenticationMethods.ClientSecretPost] - } + { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit grant type was set, assume the provider only supports the code flow. + _ => [GrantTypes.AuthorizationCode] }, - _ => null + ResponseModesSupported = configuration.Elements("ResponseMode").ToList() switch + { + { Count: > 0 } modes => modes.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit response mode was set, assume the provider only supports the query response mode. + _ => [ResponseModes.Query] + }, + + ResponseTypesSupported = configuration.Elements("ResponseType").ToList() switch + { + { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit response type was set, assume the provider only supports the code flow. + _ => [ResponseTypes.Code] + }, + + ScopesSupported = configuration.Elements("Scope").ToList() switch + { + { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(), + + _ => [] + }, + + DeviceAuthorizationEndpointAuthMethodsSupported = configuration.Elements("DeviceAuthorizationEndpointAuthMethod").ToList() switch + { + { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit client authentication method was set, assume the provider only supports + // flowing the client credentials as part of the device authorization request payload. + _ => [ClientAuthenticationMethods.ClientSecretPost] + }, + + IntrospectionEndpointAuthMethodsSupported = configuration.Elements("IntrospectionEndpointAuthMethod").ToList() switch + { + { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit client authentication method was set, assume the provider only + // supports flowing the client credentials as part of the introspection request payload. + _ => [ClientAuthenticationMethods.ClientSecretPost] + }, + + RevocationEndpointAuthMethodsSupported = configuration.Elements("RevocationEndpointAuthMethod").ToList() switch + { + { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit client authentication method was set, assume the provider only + // supports flowing the client credentials as part of the revocation request payload. + _ => [ClientAuthenticationMethods.ClientSecretPost] + }, + + TokenEndpointAuthMethodsSupported = configuration.Elements("TokenEndpointAuthMethod").ToList() switch + { + { Count: > 0 } methods => methods.Select(type => (string?) type.Attribute("Value")).ToList(), + + // If no explicit client authentication method was set, assume the provider only + // supports flowing the client credentials as part of the token request payload. + _ => [ClientAuthenticationMethods.ClientSecretPost] + } }, - Scopes = environment.Elements("Scope").Select(setting => new - { - Name = (string) setting.Attribute("Name"), - Default = (bool?) setting.Attribute("Default") ?? false, - Required = (bool?) setting.Attribute("Required") ?? false - }) - }) - .ToList(), + _ => null + }, - Settings = provider.Elements("Setting").Select(setting => new + Scopes = environment.Elements("Scope").Select(setting => new { - PropertyName = (string) setting.Attribute("PropertyName"), + Name = (string) setting.Attribute("Name"), + Default = (bool?) setting.Attribute("Default") ?? false, + Required = (bool?) setting.Attribute("Required") ?? false + }) + }) + .ToList(), - Type = (string) setting.Attribute("Type"), - Required = (bool?) setting.Attribute("Required") ?? false, - Collection = (bool?) setting.Attribute("Collection") ?? false, + Settings = provider.Elements("Setting").Select(setting => new + { + PropertyName = (string) setting.Attribute("PropertyName"), - EncryptionAlgorithm = (string?) setting.Element("EncryptionAlgorithm")?.Attribute("Value"), - SigningAlgorithm = (string?) setting.Element("SigningAlgorithm")?.Attribute("Value"), + Type = (string) setting.Attribute("Type"), + Required = (bool?) setting.Attribute("Required") ?? false, + Collection = (bool?) setting.Attribute("Collection") ?? false, - DefaultValue = (string?) setting.Attribute("DefaultValue"), + EncryptionAlgorithm = (string?) setting.Element("EncryptionAlgorithm")?.Attribute("Value"), + SigningAlgorithm = (string?) setting.Element("SigningAlgorithm")?.Attribute("Value"), - Items = setting.Elements("Item").Select(item => new - { - Value = (string) item.Attribute("Value"), - Default = (bool?) item.Attribute("Default") ?? false, - Required = (bool?) item.Attribute("Required") ?? false - }) - .ToList() + DefaultValue = (string?) setting.Attribute("DefaultValue"), + + Items = setting.Elements("Item").Select(item => new + { + Value = (string) item.Attribute("Value"), + Default = (bool?) item.Attribute("Default") ?? false, + Required = (bool?) item.Attribute("Required") ?? false }) .ToList() }) .ToList() - }); - } + }) + .ToList() + }); + } - static string GenerateHelpers(XDocument document) - { - var template = Template.Parse(@"#nullable enable + static string GenerateHelpers(XDocument document) + { + var template = Template.Parse(@"#nullable enable using Microsoft.IdentityModel.Tokens; using OpenIddict.Client; @@ -1283,21 +1283,21 @@ public static partial class OpenIddictClientWebIntegrationHelpers {{~ end ~}} } "); - return template.Render(new - { - Providers = document.Root.Elements("Provider") - .Select(provider => new - { - Name = (string) provider.Attribute("Name"), - DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name") - }) - .ToList() - }); - } - - static string GenerateSettings(XDocument document) + return template.Render(new { - var template = Template.Parse(@"#nullable enable + Providers = document.Root.Elements("Provider") + .Select(provider => new + { + Name = (string) provider.Attribute("Name"), + DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name") + }) + .ToList() + }); + } + + static string GenerateSettings(XDocument document) + { + var template = Template.Parse(@"#nullable enable using System.Security.Cryptography.X509Certificates; using Microsoft.IdentityModel.Tokens; @@ -1335,52 +1335,51 @@ public sealed partial class OpenIddictClientWebIntegrationSettings {{~ end ~}} } "); - return template.Render(new - { - Providers = document.Root.Elements("Provider") - .Select(provider => new - { - Name = (string) provider.Attribute("Name"), - DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), + return template.Render(new + { + Providers = document.Root.Elements("Provider") + .Select(provider => new + { + Name = (string) provider.Attribute("Name"), + DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), - Settings = provider.Elements("Setting").Select(setting => new - { - PropertyName = (string) setting.Attribute("PropertyName"), + Settings = provider.Elements("Setting").Select(setting => new + { + PropertyName = (string) setting.Attribute("PropertyName"), - Collection = (bool?) setting.Attribute("Collection") ?? false, - Obsolete = (bool?) setting.Attribute("Obsolete") ?? false, + Collection = (bool?) setting.Attribute("Collection") ?? false, + Obsolete = (bool?) setting.Attribute("Obsolete") ?? false, - Description = (string) setting.Attribute("Description") is string description ? - char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null, - ClrType = (string) setting.Attribute("Type") switch - { - "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value") - is "RS256" or "RS384" or "RS512" => "RsaSecurityKey", + Description = (string) setting.Attribute("Description") is string description ? + char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null, + ClrType = (string) setting.Attribute("Type") switch + { + "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value") + is "RS256" or "RS384" or "RS512" => "RsaSecurityKey", - "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") - is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey", + "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") + is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey", - "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") - is "PS256" or "PS384" or "PS512" or - "RS256" or "RS384" or "RS512" => "RsaSecurityKey", + "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") + is "PS256" or "PS384" or "PS512" or + "RS256" or "RS384" or "RS512" => "RsaSecurityKey", - "Certificate" => "X509Certificate2", - "String" => "string", - "StringHashSet" => "HashSet", - "Uri" => "Uri", + "Certificate" => "X509Certificate2", + "String" => "string", + "StringHashSet" => "HashSet", + "Uri" => "Uri", - string value => value - } - }) - .ToList() + string value => value + } }) .ToList() - }); - } + }) + .ToList() + }); } + } - public void Initialize(GeneratorInitializationContext context) - { - } + public void Initialize(GeneratorInitializationContext context) + { } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/BundleConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/BundleConfig.cs index 92ae35c8..30b0acae 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/BundleConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/BundleConfig.cs @@ -1,29 +1,28 @@ using System.Web.Optimization; -namespace OpenIddict.Sandbox.AspNet.Client +namespace OpenIddict.Sandbox.AspNet.Client; + +public class BundleConfig { - public class BundleConfig + // Pour plus d'informations sur le regroupement, visitez https://go.microsoft.com/fwlink/?LinkId=301862 + public static void RegisterBundles(BundleCollection bundles) { - // Pour plus d'informations sur le regroupement, visitez https://go.microsoft.com/fwlink/?LinkId=301862 - public static void RegisterBundles(BundleCollection bundles) - { - bundles.Add(new ScriptBundle("~/bundles/jquery").Include( - "~/Scripts/jquery-{version}.js")); + bundles.Add(new ScriptBundle("~/bundles/jquery").Include( + "~/Scripts/jquery-{version}.js")); - bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( - "~/Scripts/jquery.validate*")); + bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( + "~/Scripts/jquery.validate*")); - // Utilisez la version de développement de Modernizr pour développer et apprendre. Puis, lorsque vous êtes - // prêt pour la production, utilisez l'outil de génération à l'adresse https://modernizr.com pour sélectionner uniquement les tests dont vous avez besoin. - bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( - "~/Scripts/modernizr-*")); + // Utilisez la version de développement de Modernizr pour développer et apprendre. Puis, lorsque vous êtes + // prêt pour la production, utilisez l'outil de génération à l'adresse https://modernizr.com pour sélectionner uniquement les tests dont vous avez besoin. + bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( + "~/Scripts/modernizr-*")); - bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( - "~/Scripts/bootstrap.js")); + bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( + "~/Scripts/bootstrap.js")); - bundles.Add(new StyleBundle("~/Content/css").Include( - "~/Content/bootstrap.css", - "~/Content/site.css")); - } + bundles.Add(new StyleBundle("~/Content/css").Include( + "~/Content/bootstrap.css", + "~/Content/site.css")); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/FilterConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/FilterConfig.cs index 8c732307..e16ad29a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/FilterConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/FilterConfig.cs @@ -1,12 +1,11 @@ using System.Web.Mvc; -namespace OpenIddict.Sandbox.AspNet.Client +namespace OpenIddict.Sandbox.AspNet.Client; + +public class FilterConfig { - public class FilterConfig + public static void RegisterGlobalFilters(GlobalFilterCollection filters) { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) - { - filters.Add(new HandleErrorAttribute()); - } + filters.Add(new HandleErrorAttribute()); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/RouteConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/RouteConfig.cs index fc9cd975..f30c1a0c 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/RouteConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/RouteConfig.cs @@ -1,21 +1,20 @@ using System.Web.Mvc; using System.Web.Routing; -namespace OpenIddict.Sandbox.AspNet.Client +namespace OpenIddict.Sandbox.AspNet.Client; + +public class RouteConfig { - public class RouteConfig + public static void RegisterRoutes(RouteCollection routes) { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - routes.MapMvcAttributeRoutes(); + routes.MapMvcAttributeRoutes(); - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } - ); - } + routes.MapRoute( + name: "Default", + url: "{controller}/{action}/{id}", + defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } + ); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs index afa0c2b6..14d4c281 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs @@ -11,212 +11,211 @@ using OpenIddict.Client; using OpenIddict.Client.Owin; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Sandbox.AspNet.Client.Controllers +namespace OpenIddict.Sandbox.AspNet.Client.Controllers; + +public class AuthenticationController : Controller { - public class AuthenticationController : Controller - { - private readonly OpenIddictClientService _service; + private readonly OpenIddictClientService _service; - public AuthenticationController(OpenIddictClientService service) - => _service = service; + public AuthenticationController(OpenIddictClientService service) + => _service = service; - [HttpPost, Route("~/login"), ValidateAntiForgeryToken] - public async Task LogIn(string provider, string returnUrl) - { - var context = HttpContext.GetOwinContext(); + [HttpPost, Route("~/login"), ValidateAntiForgeryToken] + public async Task LogIn(string provider, string returnUrl) + { + var context = HttpContext.GetOwinContext(); - // The local authorization server sample allows the client to select the external - // identity provider that will be used to eventually authenticate the user. For that, - // a custom "identity_provider" parameter is sent to the authorization server so that - // the user is directly redirected to GitHub (in this case, no login page is shown). - if (string.Equals(provider, "Local+GitHub", StringComparison.Ordinal)) + // The local authorization server sample allows the client to select the external + // identity provider that will be used to eventually authenticate the user. For that, + // a custom "identity_provider" parameter is sent to the authorization server so that + // the user is directly redirected to GitHub (in this case, no login page is shown). + if (string.Equals(provider, "Local+GitHub", StringComparison.Ordinal)) + { + var properties = new AuthenticationProperties(new Dictionary { - var properties = new AuthenticationProperties(new Dictionary - { - // Note: when only one client is registered in the client options, - // specifying the issuer URI or the provider name is not required. - [OpenIddictClientOwinConstants.Properties.ProviderName] = "Local", - - // Note: the OWIN host requires appending the #string suffix to indicate - // that the "identity_provider" property is a public string parameter. - [Parameters.IdentityProvider + OpenIddictClientOwinConstants.PropertyTypes.String] = "GitHub" - }) - { - // Only allow local return URLs to prevent open redirect attacks. - RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" - }; - - // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. - context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType); - return new EmptyResult(); - } - - else + // Note: when only one client is registered in the client options, + // specifying the issuer URI or the provider name is not required. + [OpenIddictClientOwinConstants.Properties.ProviderName] = "Local", + + // Note: the OWIN host requires appending the #string suffix to indicate + // that the "identity_provider" property is a public string parameter. + [Parameters.IdentityProvider + OpenIddictClientOwinConstants.PropertyTypes.String] = "GitHub" + }) { - // Note: OpenIddict always validates the specified provider name when handling the challenge operation, - // but the provider can also be validated earlier to return an error page or a special HTTP error code. - var registrations = await _service.GetClientRegistrationsAsync(); - if (!registrations.Any(registration => string.Equals(registration.ProviderName, provider, StringComparison.Ordinal))) - { - return new HttpStatusCodeResult(400); - } - - var properties = new AuthenticationProperties(new Dictionary - { - // Note: when only one client is registered in the client options, - // specifying the issuer URI or the provider name is not required. - [OpenIddictClientOwinConstants.Properties.ProviderName] = provider - }) - { - // Only allow local return URLs to prevent open redirect attacks. - RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" - }; - - // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. - context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType); - return new EmptyResult(); - } + // Only allow local return URLs to prevent open redirect attacks. + RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" + }; + + // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. + context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType); + return new EmptyResult(); } - [HttpPost, Route("~/logout"), ValidateAntiForgeryToken] - public async Task LogOut(string returnUrl) + else { - var context = HttpContext.GetOwinContext(); + // Note: OpenIddict always validates the specified provider name when handling the challenge operation, + // but the provider can also be validated earlier to return an error page or a special HTTP error code. + var registrations = await _service.GetClientRegistrationsAsync(); + if (!registrations.Any(registration => string.Equals(registration.ProviderName, provider, StringComparison.Ordinal))) + { + return new HttpStatusCodeResult(400); + } - // Retrieve the identity stored in the local authentication cookie. If it's not available, - // this indicate that the user is already logged out locally (or has not logged in yet). - var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); - if (result is not { Identity: ClaimsIdentity identity }) + var properties = new AuthenticationProperties(new Dictionary + { + // Note: when only one client is registered in the client options, + // specifying the issuer URI or the provider name is not required. + [OpenIddictClientOwinConstants.Properties.ProviderName] = provider + }) { // Only allow local return URLs to prevent open redirect attacks. - return Redirect(Url.IsLocalUrl(returnUrl) ? returnUrl : "/"); - } + RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" + }; - // Remove the local authentication cookie before triggering a redirection to the remote server. - context.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType); + // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. + context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType); + return new EmptyResult(); + } + } - // Extract the client registration identifier and retrieve the associated server configuration. - // If the provider is known to support remote sign-out, ask OpenIddict to initiate a logout request. - if (identity.FindFirst(Claims.Private.RegistrationId)?.Value is string identifier && - await _service.GetServerConfigurationByRegistrationIdAsync(identifier) is { EndSessionEndpoint: Uri }) - { - var properties = new AuthenticationProperties(new Dictionary - { - [OpenIddictClientOwinConstants.Properties.RegistrationId] = identifier, - - // While not required, the specification encourages sending an id_token_hint - // parameter containing an identity token returned by the server for this user. - [OpenIddictClientOwinConstants.Properties.IdentityTokenHint] = - result.Properties.Dictionary[OpenIddictClientOwinConstants.Tokens.BackchannelIdentityToken] - }) - { - // Only allow local return URLs to prevent open redirect attacks. - RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" - }; - - // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. - context.Authentication.SignOut(properties, OpenIddictClientOwinDefaults.AuthenticationType); - return new EmptyResult(); - } + [HttpPost, Route("~/logout"), ValidateAntiForgeryToken] + public async Task LogOut(string returnUrl) + { + var context = HttpContext.GetOwinContext(); + // Retrieve the identity stored in the local authentication cookie. If it's not available, + // this indicate that the user is already logged out locally (or has not logged in yet). + var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); + if (result is not { Identity: ClaimsIdentity identity }) + { // Only allow local return URLs to prevent open redirect attacks. return Redirect(Url.IsLocalUrl(returnUrl) ? returnUrl : "/"); } - // Note: this controller uses the same callback action for all providers - // but for users who prefer using a different action per provider, - // the following action can be split into separate actions. - [AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")] - public async Task LogInCallback() + // Remove the local authentication cookie before triggering a redirection to the remote server. + context.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType); + + // Extract the client registration identifier and retrieve the associated server configuration. + // If the provider is known to support remote sign-out, ask OpenIddict to initiate a logout request. + if (identity.FindFirst(Claims.Private.RegistrationId)?.Value is string identifier && + await _service.GetServerConfigurationByRegistrationIdAsync(identifier) is { EndSessionEndpoint: Uri }) { - var context = HttpContext.GetOwinContext(); + var properties = new AuthenticationProperties(new Dictionary + { + [OpenIddictClientOwinConstants.Properties.RegistrationId] = identifier, - // Retrieve the authorization data validated by OpenIddict as part of the callback handling. - var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); + // While not required, the specification encourages sending an id_token_hint + // parameter containing an identity token returned by the server for this user. + [OpenIddictClientOwinConstants.Properties.IdentityTokenHint] = + result.Properties.Dictionary[OpenIddictClientOwinConstants.Tokens.BackchannelIdentityToken] + }) + { + // Only allow local return URLs to prevent open redirect attacks. + RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/" + }; - // Multiple strategies exist to handle OAuth 2.0/OpenID Connect callbacks, each with their pros and cons: - // - // * Directly using the tokens to perform the necessary action(s) on behalf of the user, which is suitable - // for applications that don't need a long-term access to the user's resources or don't want to store - // access/refresh tokens in a database or in an authentication cookie (which has security implications). - // It is also suitable for applications that don't need to authenticate users but only need to perform - // action(s) on their behalf by making API calls using the access token returned by the remote server. - // - // * Storing the external claims/tokens in a database (and optionally keeping the essential claims in an - // authentication cookie so that cookie size limits are not hit). For the applications that use ASP.NET - // Core Identity, the UserManager.SetAuthenticationTokenAsync() API can be used to store external tokens. - // - // Note: in this case, it's recommended to use column encryption to protect the tokens in the database. + // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. + context.Authentication.SignOut(properties, OpenIddictClientOwinDefaults.AuthenticationType); + return new EmptyResult(); + } + + // Only allow local return URLs to prevent open redirect attacks. + return Redirect(Url.IsLocalUrl(returnUrl) ? returnUrl : "/"); + } + + // Note: this controller uses the same callback action for all providers + // but for users who prefer using a different action per provider, + // the following action can be split into separate actions. + [AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")] + public async Task LogInCallback() + { + var context = HttpContext.GetOwinContext(); + + // Retrieve the authorization data validated by OpenIddict as part of the callback handling. + var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); + + // Multiple strategies exist to handle OAuth 2.0/OpenID Connect callbacks, each with their pros and cons: + // + // * Directly using the tokens to perform the necessary action(s) on behalf of the user, which is suitable + // for applications that don't need a long-term access to the user's resources or don't want to store + // access/refresh tokens in a database or in an authentication cookie (which has security implications). + // It is also suitable for applications that don't need to authenticate users but only need to perform + // action(s) on their behalf by making API calls using the access token returned by the remote server. + // + // * Storing the external claims/tokens in a database (and optionally keeping the essential claims in an + // authentication cookie so that cookie size limits are not hit). For the applications that use ASP.NET + // Core Identity, the UserManager.SetAuthenticationTokenAsync() API can be used to store external tokens. + // + // Note: in this case, it's recommended to use column encryption to protect the tokens in the database. + // + // * Storing the external claims/tokens in an authentication cookie, which doesn't require having + // a user database but may be affected by the cookie size limits enforced by most browser vendors + // (e.g Safari for macOS and Safari for iOS/iPadOS enforce a per-domain 4KB limit for all cookies). + // + // Note: this is the approach used here, but the external claims are first filtered to only persist + // a few claims like the user identifier. The same approach is used to store the access/refresh tokens. + + // Important: if the remote server doesn't support OpenID Connect and doesn't expose a userinfo endpoint, + // result.Principal.Identity will represent an unauthenticated identity and won't contain any user claim. + // + // Such identities cannot be used as-is to build an authentication cookie in ASP.NET (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.Identity is not ClaimsIdentity { IsAuthenticated: true }) + { + throw new InvalidOperationException("The external authorization data cannot be used for authentication."); + } + + // Build an identity based on the external claims and that will be used to create the authentication cookie. + // + // By default, all claims extracted during the authorization dance are available. The claims collection stored + // in the cookie can be filtered out or mapped to different names depending the claim name or its issuer. + var claims = result.Identity.Claims.Where(claim => claim.Type is ClaimTypes.NameIdentifier or ClaimTypes.Name // - // * Storing the external claims/tokens in an authentication cookie, which doesn't require having - // a user database but may be affected by the cookie size limits enforced by most browser vendors - // (e.g Safari for macOS and Safari for iOS/iPadOS enforce a per-domain 4KB limit for all cookies). + // Preserve the registration details to be able to resolve them later. // - // Note: this is the approach used here, but the external claims are first filtered to only persist - // a few claims like the user identifier. The same approach is used to store the access/refresh tokens. - - // Important: if the remote server doesn't support OpenID Connect and doesn't expose a userinfo endpoint, - // result.Principal.Identity will represent an unauthenticated identity and won't contain any user claim. + or Claims.Private.RegistrationId or Claims.Private.ProviderName // - // Such identities cannot be used as-is to build an authentication cookie in ASP.NET (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.Identity is not ClaimsIdentity { IsAuthenticated: true }) - { - throw new InvalidOperationException("The external authorization data cannot be used for authentication."); - } - - // Build an identity based on the external claims and that will be used to create the authentication cookie. + // The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim. // - // By default, all claims extracted during the authorization dance are available. The claims collection stored - // in the cookie can be filtered out or mapped to different names depending the claim name or its issuer. - var claims = result.Identity.Claims.Where(claim => claim.Type is ClaimTypes.NameIdentifier or ClaimTypes.Name - // - // Preserve the registration details to be able to resolve them later. - // - or Claims.Private.RegistrationId or Claims.Private.ProviderName - // - // The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim. - // - or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"); - - var identity = new ClaimsIdentity(claims, - authenticationType: CookieAuthenticationDefaults.AuthenticationType, - nameType: ClaimTypes.Name, - roleType: ClaimTypes.Role); - - // Build the authentication properties based on the properties that were added when the challenge was triggered. - var properties = new AuthenticationProperties(result.Properties.Dictionary - .Where(item => item.Key is - // Preserve the return URL. - ".redirect" or - - // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. - OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or - OpenIddictClientOwinConstants.Tokens.BackchannelIdentityToken or - OpenIddictClientOwinConstants.Tokens.RefreshToken) - .ToDictionary(pair => pair.Key, pair => pair.Value)); - - context.Authentication.SignIn(properties, identity); - return Redirect(properties.RedirectUri ?? "/"); - } + or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"); + + var identity = new ClaimsIdentity(claims, + authenticationType: CookieAuthenticationDefaults.AuthenticationType, + nameType: ClaimTypes.Name, + roleType: ClaimTypes.Role); + + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Dictionary + .Where(item => item.Key is + // Preserve the return URL. + ".redirect" or + + // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or + OpenIddictClientOwinConstants.Tokens.BackchannelIdentityToken or + OpenIddictClientOwinConstants.Tokens.RefreshToken) + .ToDictionary(pair => pair.Key, pair => pair.Value)); + + context.Authentication.SignIn(properties, identity); + return Redirect(properties.RedirectUri ?? "/"); + } - // Note: this controller uses the same callback action for all providers - // but for users who prefer using a different action per provider, - // the following action can be split into separate actions. - [AcceptVerbs("GET", "POST"), Route("~/callback/logout/{provider}")] - public async Task LogOutCallback() - { - var context = HttpContext.GetOwinContext(); + // Note: this controller uses the same callback action for all providers + // but for users who prefer using a different action per provider, + // the following action can be split into separate actions. + [AcceptVerbs("GET", "POST"), Route("~/callback/logout/{provider}")] + public async Task LogOutCallback() + { + var context = HttpContext.GetOwinContext(); - // Retrieve the data stored by OpenIddict in the state token created when the logout was triggered. - var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); + // Retrieve the data stored by OpenIddict in the state token created when the logout was triggered. + var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); - // In this sample, the local authentication cookie is always removed before the user agent is redirected - // to the authorization server. Applications that prefer delaying the removal of the local cookie can - // remove the corresponding code from the logout action and remove the authentication cookie in this action. + // In this sample, the local authentication cookie is always removed before the user agent is redirected + // to the authorization server. Applications that prefer delaying the removal of the local cookie can + // remove the corresponding code from the logout action and remove the authentication cookie in this action. - return Redirect(result.Properties.RedirectUri ?? "/"); - } + return Redirect(result.Properties.RedirectUri ?? "/"); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs index fcdb7aa5..fb52619c 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs @@ -13,97 +13,96 @@ using OpenIddict.Sandbox.AspNet.Client.ViewModels.Home; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; -namespace OpenIddict.Sandbox.AspNet.Client.Controllers +namespace OpenIddict.Sandbox.AspNet.Client.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + private readonly IHttpClientFactory _httpClientFactory; + private readonly OpenIddictClientService _service; + + public HomeController( + IHttpClientFactory httpClientFactory, + OpenIddictClientService service) { - private readonly IHttpClientFactory _httpClientFactory; - private readonly OpenIddictClientService _service; + _httpClientFactory = httpClientFactory; + _service = service; + } - public HomeController( - IHttpClientFactory httpClientFactory, - OpenIddictClientService service) - { - _httpClientFactory = httpClientFactory; - _service = service; - } + [HttpGet, Route("~/")] + public async Task Index(CancellationToken cancellationToken) => View(new IndexViewModel + { + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); + + [Authorize, HttpPost, Route("~/message"), ValidateAntiForgeryToken] + public async Task GetMessage(CancellationToken cancellationToken) + { + var context = HttpContext.GetOwinContext(); - [HttpGet, Route("~/")] - public async Task Index(CancellationToken cancellationToken) => View(new IndexViewModel + var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); + var token = result.Properties.Dictionary[Tokens.BackchannelAccessToken]; + + using var client = _httpClientFactory.CreateClient(); + + using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44349/api/message"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + using var response = await client.SendAsync(request, cancellationToken); + response.EnsureSuccessStatusCode(); + + return View("Index", new IndexViewModel { + Message = await response.Content.ReadAsStringAsync(), Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) where !string.IsNullOrEmpty(registration.ProviderName) where !string.IsNullOrEmpty(registration.ProviderDisplayName) select registration }); + } - [Authorize, HttpPost, Route("~/message"), ValidateAntiForgeryToken] - public async Task GetMessage(CancellationToken cancellationToken) - { - var context = HttpContext.GetOwinContext(); + [Authorize, HttpPost, Route("~/refresh-token")] + [ValidateAntiForgeryToken] + public async Task RefreshToken(CancellationToken cancellationToken) + { + var context = HttpContext.GetOwinContext(); - var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); - var token = result.Properties.Dictionary[Tokens.BackchannelAccessToken]; + var ticket = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); + if (!ticket.Properties.Dictionary.TryGetValue(Tokens.RefreshToken, out string token)) + { + return new HttpStatusCodeResult(400); + } - using var client = _httpClientFactory.CreateClient(); + var result = await _service.AuthenticateWithRefreshTokenAsync(new() + { + CancellationToken = cancellationToken, + RefreshToken = token, + RegistrationId = ticket.Identity.FindFirst(Claims.Private.RegistrationId)?.Value + }); - using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44349/api/message"); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + var properties = new AuthenticationProperties(ticket.Properties.Dictionary) + { + RedirectUri = null + }; - using var response = await client.SendAsync(request, cancellationToken); - response.EnsureSuccessStatusCode(); + properties.Dictionary[Tokens.BackchannelAccessToken] = result.AccessToken; - return View("Index", new IndexViewModel - { - Message = await response.Content.ReadAsStringAsync(), - Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) - where !string.IsNullOrEmpty(registration.ProviderName) - where !string.IsNullOrEmpty(registration.ProviderDisplayName) - select registration - }); + if (!string.IsNullOrEmpty(result.RefreshToken)) + { + properties.Dictionary[Tokens.RefreshToken] = result.RefreshToken; } - [Authorize, HttpPost, Route("~/refresh-token")] - [ValidateAntiForgeryToken] - public async Task RefreshToken(CancellationToken cancellationToken) + context.Authentication.SignIn(properties, ticket.Identity); + + return View("Index", new IndexViewModel { - var context = HttpContext.GetOwinContext(); - - var ticket = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); - if (!ticket.Properties.Dictionary.TryGetValue(Tokens.RefreshToken, out string token)) - { - return new HttpStatusCodeResult(400); - } - - var result = await _service.AuthenticateWithRefreshTokenAsync(new() - { - CancellationToken = cancellationToken, - RefreshToken = token, - RegistrationId = ticket.Identity.FindFirst(Claims.Private.RegistrationId)?.Value - }); - - var properties = new AuthenticationProperties(ticket.Properties.Dictionary) - { - RedirectUri = null - }; - - properties.Dictionary[Tokens.BackchannelAccessToken] = result.AccessToken; - - if (!string.IsNullOrEmpty(result.RefreshToken)) - { - properties.Dictionary[Tokens.RefreshToken] = result.RefreshToken; - } - - context.Authentication.SignIn(properties, ticket.Identity); - - return View("Index", new IndexViewModel - { - Message = result.AccessToken, - Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) - where !string.IsNullOrEmpty(registration.ProviderName) - where !string.IsNullOrEmpty(registration.ProviderDisplayName) - select registration - }); - } + Message = result.AccessToken, + Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken) + where !string.IsNullOrEmpty(registration.ProviderName) + where !string.IsNullOrEmpty(registration.ProviderDisplayName) + select registration + }); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Global.asax.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Global.asax.cs index e422f97a..c631e704 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Global.asax.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Global.asax.cs @@ -2,16 +2,15 @@ using System.Web.Optimization; using System.Web.Routing; -namespace OpenIddict.Sandbox.AspNet.Client +namespace OpenIddict.Sandbox.AspNet.Client; + +public class MvcApplication : System.Web.HttpApplication { - public class MvcApplication : System.Web.HttpApplication + protected void Application_Start() { - protected void Application_Start() - { - AreaRegistration.RegisterAllAreas(); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); - RouteConfig.RegisterRoutes(RouteTable.Routes); - BundleConfig.RegisterBundles(BundleTable.Bundles); - } + AreaRegistration.RegisterAllAreas(); + FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + RouteConfig.RegisterRoutes(RouteTable.Routes); + BundleConfig.RegisterBundles(BundleTable.Bundles); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs index d9e16fe4..d68bf476 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs @@ -1,23 +1,22 @@ using System.Data.Entity; -namespace OpenIddict.Sandbox.AspNetCore.Server.Models +namespace OpenIddict.Sandbox.AspNetCore.Server.Models; + +public class ApplicationDbContext : DbContext { - public class ApplicationDbContext : DbContext + public ApplicationDbContext() + : base("DefaultConnection") { - public ApplicationDbContext() - : base("DefaultConnection") - { - } + } - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - modelBuilder.UseOpenIddict(); + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + modelBuilder.UseOpenIddict(); - base.OnModelCreating(modelBuilder); + base.OnModelCreating(modelBuilder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs index 8a8b8632..67fd740f 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs @@ -13,127 +13,126 @@ using OpenIddict.Sandbox.AspNetCore.Server.Models; using Owin; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Sandbox.AspNet.Client +namespace OpenIddict.Sandbox.AspNet.Client; + +public class Startup { - public class Startup + public void Configuration(IAppBuilder app) { - public void Configuration(IAppBuilder app) - { - var services = new ServiceCollection(); + var services = new ServiceCollection(); - services.AddOpenIddict() + services.AddOpenIddict() - // Register the OpenIddict core components. - .AddCore(options => - { - // Configure OpenIddict to use the Entity Framework 6.x stores and models. - // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. - options.UseEntityFramework() - .UseDbContext(); - - // Developers who prefer using MongoDB can remove the previous lines - // and configure OpenIddict to use the specified MongoDB database: - // options.UseMongoDb() - // .UseDatabase(new MongoClient().GetDatabase("openiddict")); - }) - - // Register the OpenIddict client components. - .AddClient(options => + // Register the OpenIddict core components. + .AddCore(options => + { + // Configure OpenIddict to use the Entity Framework 6.x stores and models. + // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. + options.UseEntityFramework() + .UseDbContext(); + + // Developers who prefer using MongoDB can remove the previous lines + // and configure OpenIddict to use the specified MongoDB database: + // options.UseMongoDb() + // .UseDatabase(new MongoClient().GetDatabase("openiddict")); + }) + + // Register the OpenIddict client components. + .AddClient(options => + { + // Note: this sample uses the authorization code and refresh token + // flows, but you can enable the other flows if necessary. + options.AllowAuthorizationCodeFlow() + .AllowRefreshTokenFlow(); + + // Register the signing and encryption credentials used to protect + // sensitive data like the state tokens produced by OpenIddict. + options.AddDevelopmentEncryptionCertificate() + .AddDevelopmentSigningCertificate(); + + // Register the OWIN host and configure the OWIN-specific options. + options.UseOwin() + .EnableRedirectionEndpointPassthrough() + .EnablePostLogoutRedirectionEndpointPassthrough() + .SetCookieManager(new SystemWebCookieManager()); + + // Register the System.Net.Http integration and use the identity of the current + // assembly as a more specific user agent, which can be useful when dealing with + // providers that use the user agent as a way to throttle requests (e.g Reddit). + options.UseSystemNetHttp() + .SetProductInformation(typeof(Startup).Assembly); + + // Add a client registration matching the client application definition in the server project. + options.AddRegistration(new OpenIddictClientRegistration { - // Note: this sample uses the authorization code and refresh token - // flows, but you can enable the other flows if necessary. - options.AllowAuthorizationCodeFlow() - .AllowRefreshTokenFlow(); - - // Register the signing and encryption credentials used to protect - // sensitive data like the state tokens produced by OpenIddict. - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - // Register the OWIN host and configure the OWIN-specific options. - options.UseOwin() - .EnableRedirectionEndpointPassthrough() - .EnablePostLogoutRedirectionEndpointPassthrough() - .SetCookieManager(new SystemWebCookieManager()); - - // Register the System.Net.Http integration and use the identity of the current - // assembly as a more specific user agent, which can be useful when dealing with - // providers that use the user agent as a way to throttle requests (e.g Reddit). - options.UseSystemNetHttp() - .SetProductInformation(typeof(Startup).Assembly); - - // Add a client registration matching the client application definition in the server project. - options.AddRegistration(new OpenIddictClientRegistration - { - Issuer = new Uri("https://localhost:44349/", UriKind.Absolute), - ProviderName = "Local", - ProviderDisplayName = "Local OIDC server", - - ClientId = "mvc", - ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", - Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }, - - RedirectUri = new Uri("callback/login/local", UriKind.Relative), - PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative) - }); - - // Register the Web providers integrations. - // - // Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint - // URI per provider, unless all the registered providers support returning a special "iss" - // parameter containing their URL as part of authorization responses. For more information, - // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4. - options.UseWebProviders() - .AddGitHub(options => - { - options.SetClientId("c4ade52327b01ddacff3") - .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") - .SetRedirectUri("callback/login/github"); - }) - .AddGoogle(options => - { - options.SetClientId("1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com") - .SetClientSecret("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") - .SetRedirectUri("callback/login/google") - .SetAccessType("offline") - .AddScopes(Scopes.Profile); - }); + Issuer = new Uri("https://localhost:44349/", UriKind.Absolute), + ProviderName = "Local", + ProviderDisplayName = "Local OIDC server", + + ClientId = "mvc", + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", + Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }, + + RedirectUri = new Uri("callback/login/local", UriKind.Relative), + PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative) }); - // Create a new Autofac container and import the OpenIddict services. - var builder = new ContainerBuilder(); - builder.Populate(services); + // Register the Web providers integrations. + // + // Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint + // URI per provider, unless all the registered providers support returning a special "iss" + // parameter containing their URL as part of authorization responses. For more information, + // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4. + options.UseWebProviders() + .AddGitHub(options => + { + options.SetClientId("c4ade52327b01ddacff3") + .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") + .SetRedirectUri("callback/login/github"); + }) + .AddGoogle(options => + { + options.SetClientId("1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com") + .SetClientSecret("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") + .SetRedirectUri("callback/login/google") + .SetAccessType("offline") + .AddScopes(Scopes.Profile); + }); + }); - // Register the MVC controllers. - builder.RegisterControllers(typeof(Startup).Assembly); + // Create a new Autofac container and import the OpenIddict services. + var builder = new ContainerBuilder(); + builder.Populate(services); - var container = builder.Build(); + // Register the MVC controllers. + builder.RegisterControllers(typeof(Startup).Assembly); - // Register the Autofac scope injector middleware. - app.UseAutofacLifetimeScopeInjector(container); + var container = builder.Build(); - // Register the cookie middleware responsible for storing the user sessions. - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - ExpireTimeSpan = TimeSpan.FromMinutes(50), - SlidingExpiration = false - }); + // Register the Autofac scope injector middleware. + app.UseAutofacLifetimeScopeInjector(container); - // Register the OpenIddict middleware. - app.UseMiddlewareFromContainer(); + // Register the cookie middleware responsible for storing the user sessions. + app.UseCookieAuthentication(new CookieAuthenticationOptions + { + ExpireTimeSpan = TimeSpan.FromMinutes(50), + SlidingExpiration = false + }); - // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances. - DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); + // Register the OpenIddict middleware. + app.UseMiddlewareFromContainer(); - // Create the database used by the OpenIddict client stack to store tokens. - // Note: in a real world application, this step should be part of a setup script. - Task.Run(async delegate - { - await using var scope = container.BeginLifetimeScope(); + // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances. + DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); + + // Create the database used by the OpenIddict client stack to store tokens. + // Note: in a real world application, this step should be part of a setup script. + Task.Run(async delegate + { + await using var scope = container.BeginLifetimeScope(); - var context = scope.Resolve(); - context.Database.CreateIfNotExists(); - }).GetAwaiter().GetResult(); - } + var context = scope.Resolve(); + context.Database.CreateIfNotExists(); + }).GetAwaiter().GetResult(); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/BundleConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/BundleConfig.cs index b61b2e43..dcdd299b 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/BundleConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/BundleConfig.cs @@ -1,29 +1,28 @@ using System.Web.Optimization; -namespace OpenIddict.Sandbox.AspNet.Server +namespace OpenIddict.Sandbox.AspNet.Server; + +public class BundleConfig { - public class BundleConfig + // Pour plus d'informations sur le regroupement, visitez https://go.microsoft.com/fwlink/?LinkId=301862 + public static void RegisterBundles(BundleCollection bundles) { - // Pour plus d'informations sur le regroupement, visitez https://go.microsoft.com/fwlink/?LinkId=301862 - public static void RegisterBundles(BundleCollection bundles) - { - bundles.Add(new ScriptBundle("~/bundles/jquery").Include( - "~/Scripts/jquery-{version}.js")); + bundles.Add(new ScriptBundle("~/bundles/jquery").Include( + "~/Scripts/jquery-{version}.js")); - bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( - "~/Scripts/jquery.validate*")); + bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( + "~/Scripts/jquery.validate*")); - // Utilisez la version de développement de Modernizr pour développer et apprendre. Puis, lorsque vous êtes - // prêt pour la production, utilisez l'outil de génération à l'adresse https://modernizr.com pour sélectionner uniquement les tests dont vous avez besoin. - bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( - "~/Scripts/modernizr-*")); + // Utilisez la version de développement de Modernizr pour développer et apprendre. Puis, lorsque vous êtes + // prêt pour la production, utilisez l'outil de génération à l'adresse https://modernizr.com pour sélectionner uniquement les tests dont vous avez besoin. + bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( + "~/Scripts/modernizr-*")); - bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( - "~/Scripts/bootstrap.js")); + bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( + "~/Scripts/bootstrap.js")); - bundles.Add(new StyleBundle("~/Content/css").Include( - "~/Content/bootstrap.css", - "~/Content/site.css")); - } + bundles.Add(new StyleBundle("~/Content/css").Include( + "~/Content/bootstrap.css", + "~/Content/site.css")); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/FilterConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/FilterConfig.cs index 54d3a557..71911d80 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/FilterConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/FilterConfig.cs @@ -1,12 +1,11 @@ using System.Web.Mvc; -namespace OpenIddict.Sandbox.AspNet.Server +namespace OpenIddict.Sandbox.AspNet.Server; + +public class FilterConfig { - public class FilterConfig + public static void RegisterGlobalFilters(GlobalFilterCollection filters) { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) - { - filters.Add(new HandleErrorAttribute()); - } + filters.Add(new HandleErrorAttribute()); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs index c4d7dc48..7fdf2fc6 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs @@ -8,98 +8,97 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using OpenIddict.Sandbox.AspNet.Server.Models; -namespace OpenIddict.Sandbox.AspNet.Server +namespace OpenIddict.Sandbox.AspNet.Server; + +public class EmailService : IIdentityMessageService { - public class EmailService : IIdentityMessageService + public Task SendAsync(IdentityMessage message) { - public Task SendAsync(IdentityMessage message) - { - // Connectez votre service e-mail ici pour envoyer un e-mail. - return Task.FromResult(0); - } + // Connectez votre service e-mail ici pour envoyer un e-mail. + return Task.FromResult(0); } +} - public class SmsService : IIdentityMessageService +public class SmsService : IIdentityMessageService +{ + public Task SendAsync(IdentityMessage message) { - public Task SendAsync(IdentityMessage message) - { - // Connectez votre service SMS ici pour envoyer un message texte. - return Task.FromResult(0); - } + // Connectez votre service SMS ici pour envoyer un message texte. + return Task.FromResult(0); } +} - // Configurer l'application que le gestionnaire des utilisateurs a utilisée dans cette application. UserManager est défini dans ASP.NET Identity et est utilisé par l'application. - public class ApplicationUserManager : UserManager +// Configurer l'application que le gestionnaire des utilisateurs a utilisée dans cette application. UserManager est défini dans ASP.NET Identity et est utilisé par l'application. +public class ApplicationUserManager : UserManager +{ + public ApplicationUserManager(IUserStore store) + : base(store) { - public ApplicationUserManager(IUserStore store) - : base(store) - { - } + } - public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) + public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) + { + var manager = new ApplicationUserManager(new UserStore(context.Get())); + // Configurer la logique de validation pour les noms d'utilisateur + manager.UserValidator = new UserValidator(manager) { - var manager = new ApplicationUserManager(new UserStore(context.Get())); - // Configurer la logique de validation pour les noms d'utilisateur - manager.UserValidator = new UserValidator(manager) - { - AllowOnlyAlphanumericUserNames = false, - RequireUniqueEmail = true - }; + AllowOnlyAlphanumericUserNames = false, + RequireUniqueEmail = true + }; - // Configurer la logique de validation pour les mots de passe - manager.PasswordValidator = new PasswordValidator - { - RequiredLength = 6, - RequireNonLetterOrDigit = true, - RequireDigit = true, - RequireLowercase = true, - RequireUppercase = true, - }; + // Configurer la logique de validation pour les mots de passe + manager.PasswordValidator = new PasswordValidator + { + RequiredLength = 6, + RequireNonLetterOrDigit = true, + RequireDigit = true, + RequireLowercase = true, + RequireUppercase = true, + }; - // Configurer les valeurs par défaut du verrouillage de l'utilisateur - manager.UserLockoutEnabledByDefault = true; - manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); - manager.MaxFailedAccessAttemptsBeforeLockout = 5; + // Configurer les valeurs par défaut du verrouillage de l'utilisateur + manager.UserLockoutEnabledByDefault = true; + manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); + manager.MaxFailedAccessAttemptsBeforeLockout = 5; - // Inscrire les fournisseurs d'authentification à 2 facteurs. Cette application utilise le téléphone et l'e-mail comme procédure de réception d'un code de vérification de l'utilisateur - // Vous pouvez écrire votre propre fournisseur et le connecter ici. - manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider - { - MessageFormat = "Votre code de sécurité est {0}" - }); - manager.RegisterTwoFactorProvider("Code d'e-mail", new EmailTokenProvider - { - Subject = "Code de sécurité", - BodyFormat = "Votre code de sécurité est {0}" - }); - manager.EmailService = new EmailService(); - manager.SmsService = new SmsService(); - var dataProtectionProvider = options.DataProtectionProvider; - if (dataProtectionProvider != null) - { - manager.UserTokenProvider = - new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); - } - return manager; + // Inscrire les fournisseurs d'authentification à 2 facteurs. Cette application utilise le téléphone et l'e-mail comme procédure de réception d'un code de vérification de l'utilisateur + // Vous pouvez écrire votre propre fournisseur et le connecter ici. + manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider + { + MessageFormat = "Votre code de sécurité est {0}" + }); + manager.RegisterTwoFactorProvider("Code d'e-mail", new EmailTokenProvider + { + Subject = "Code de sécurité", + BodyFormat = "Votre code de sécurité est {0}" + }); + manager.EmailService = new EmailService(); + manager.SmsService = new SmsService(); + var dataProtectionProvider = options.DataProtectionProvider; + if (dataProtectionProvider != null) + { + manager.UserTokenProvider = + new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); } + return manager; } +} - // Configurer le gestionnaire de connexion d'application qui est utilisé dans cette application. - public class ApplicationSignInManager : SignInManager +// Configurer le gestionnaire de connexion d'application qui est utilisé dans cette application. +public class ApplicationSignInManager : SignInManager +{ + public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) + : base(userManager, authenticationManager) { - public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) - : base(userManager, authenticationManager) - { - } + } - public override Task CreateUserIdentityAsync(ApplicationUser user) - { - return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); - } + public override Task CreateUserIdentityAsync(ApplicationUser user) + { + return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); + } - public static ApplicationSignInManager Create(IdentityFactoryOptions options, IOwinContext context) - { - return new ApplicationSignInManager(context.GetUserManager(), context.Authentication); - } + public static ApplicationSignInManager Create(IdentityFactoryOptions options, IOwinContext context) + { + return new ApplicationSignInManager(context.GetUserManager(), context.Authentication); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/RouteConfig.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/RouteConfig.cs index 15a5fa62..2f3b3c11 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/RouteConfig.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/RouteConfig.cs @@ -1,21 +1,20 @@ using System.Web.Mvc; using System.Web.Routing; -namespace OpenIddict.Sandbox.AspNet.Server +namespace OpenIddict.Sandbox.AspNet.Server; + +public class RouteConfig { - public class RouteConfig + public static void RegisterRoutes(RouteCollection routes) { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - routes.MapMvcAttributeRoutes(); + routes.MapMvcAttributeRoutes(); - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } - ); - } + routes.MapRoute( + name: "Default", + url: "{controller}/{action}/{id}", + defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } + ); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AccountController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AccountController.cs index 195dd55a..3fb86880 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AccountController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AccountController.cs @@ -7,476 +7,475 @@ using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; using OpenIddict.Sandbox.AspNet.Server.Models; -namespace OpenIddict.Sandbox.AspNet.Server.Controllers +namespace OpenIddict.Sandbox.AspNet.Server.Controllers; + +[Authorize] +public class AccountController : Controller { - [Authorize] - public class AccountController : Controller + private ApplicationSignInManager _signInManager; + private ApplicationUserManager _userManager; + + public AccountController() { - private ApplicationSignInManager _signInManager; - private ApplicationUserManager _userManager; + } - public AccountController() - { - } + public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager ) + { + UserManager = userManager; + SignInManager = signInManager; + } - public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager ) + public ApplicationSignInManager SignInManager + { + get { - UserManager = userManager; - SignInManager = signInManager; + return _signInManager ?? HttpContext.GetOwinContext().Get(); + } + private set + { + _signInManager = value; } + } - public ApplicationSignInManager SignInManager + public ApplicationUserManager UserManager + { + get { - get - { - return _signInManager ?? HttpContext.GetOwinContext().Get(); - } - private set - { - _signInManager = value; - } + return _userManager ?? HttpContext.GetOwinContext().GetUserManager(); } - - public ApplicationUserManager UserManager + private set { - get - { - return _userManager ?? HttpContext.GetOwinContext().GetUserManager(); - } - private set - { - _userManager = value; - } + _userManager = value; } + } + + // + // GET: /Account/Login + [AllowAnonymous] + public ActionResult Login(string returnUrl) + { + ViewBag.ReturnUrl = returnUrl; + return View(); + } - // - // GET: /Account/Login - [AllowAnonymous] - public ActionResult Login(string returnUrl) + // + // POST: /Account/Login + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Login(LoginViewModel model, string returnUrl) + { + if (!ModelState.IsValid) { - ViewBag.ReturnUrl = returnUrl; - return View(); + return View(model); } - // - // POST: /Account/Login - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Login(LoginViewModel model, string returnUrl) + // Ceci ne comptabilise pas les échecs de connexion pour le verrouillage du compte + // Pour que les échecs de mot de passe déclenchent le verrouillage du compte, utilisez shouldLockout: true + var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); + switch (result) { - if (!ModelState.IsValid) - { + case SignInStatus.Success: + return RedirectToLocal(returnUrl); + case SignInStatus.LockedOut: + return View("Lockout"); + case SignInStatus.RequiresVerification: + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + case SignInStatus.Failure: + default: + ModelState.AddModelError("", "Tentative de connexion non valide."); return View(model); - } - - // Ceci ne comptabilise pas les échecs de connexion pour le verrouillage du compte - // Pour que les échecs de mot de passe déclenchent le verrouillage du compte, utilisez shouldLockout: true - var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); - switch (result) - { - case SignInStatus.Success: - return RedirectToLocal(returnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - case SignInStatus.RequiresVerification: - return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); - case SignInStatus.Failure: - default: - ModelState.AddModelError("", "Tentative de connexion non valide."); - return View(model); - } } + } - // - // GET: /Account/VerifyCode - [AllowAnonymous] - public async Task VerifyCode(string provider, string returnUrl, bool rememberMe) + // + // GET: /Account/VerifyCode + [AllowAnonymous] + public async Task VerifyCode(string provider, string returnUrl, bool rememberMe) + { + // Nécessiter que l'utilisateur soit déjà connecté via un nom d'utilisateur/mot de passe ou une connexte externe + if (!await SignInManager.HasBeenVerifiedAsync()) { - // Nécessiter que l'utilisateur soit déjà connecté via un nom d'utilisateur/mot de passe ou une connexte externe - if (!await SignInManager.HasBeenVerifiedAsync()) - { - return View("Error"); - } - return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + return View("Error"); } + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } - // - // POST: /Account/VerifyCode - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task VerifyCode(VerifyCodeViewModel model) + // + // POST: /Account/VerifyCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task VerifyCode(VerifyCodeViewModel model) + { + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } - - // Le code suivant protège des attaques par force brute contre les codes à 2 facteurs. - // Si un utilisateur entre des codes incorrects pendant un certain intervalle, le compte de cet utilisateur - // est alors verrouillé pendant une durée spécifiée. - // Vous pouvez configurer les paramètres de verrouillage du compte dans IdentityConfig - var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser); - switch (result) - { - case SignInStatus.Success: - return RedirectToLocal(model.ReturnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - case SignInStatus.Failure: - default: - ModelState.AddModelError("", "Code non valide."); - return View(model); - } + return View(model); } - // - // GET: /Account/Register - [AllowAnonymous] - public ActionResult Register() + // Le code suivant protège des attaques par force brute contre les codes à 2 facteurs. + // Si un utilisateur entre des codes incorrects pendant un certain intervalle, le compte de cet utilisateur + // est alors verrouillé pendant une durée spécifiée. + // Vous pouvez configurer les paramètres de verrouillage du compte dans IdentityConfig + var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser); + switch (result) { - return View(); + case SignInStatus.Success: + return RedirectToLocal(model.ReturnUrl); + case SignInStatus.LockedOut: + return View("Lockout"); + case SignInStatus.Failure: + default: + ModelState.AddModelError("", "Code non valide."); + return View(model); } + } + + // + // GET: /Account/Register + [AllowAnonymous] + public ActionResult Register() + { + return View(); + } - // - // POST: /Account/Register - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Register(RegisterViewModel model) + // + // POST: /Account/Register + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Register(RegisterViewModel model) + { + if (ModelState.IsValid) { - if (ModelState.IsValid) + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await UserManager.CreateAsync(user, model.Password); + if (result.Succeeded) { - var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; - var result = await UserManager.CreateAsync(user, model.Password); - if (result.Succeeded) - { - await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false); - - // Pour plus d'informations sur l'activation de la confirmation de compte et de la réinitialisation de mot de passe, visitez https://go.microsoft.com/fwlink/?LinkID=320771 - // Envoyer un e-mail avec ce lien - // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); - // var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); - // await UserManager.SendEmailAsync(user.Id, "Confirmer votre compte", "Confirmez votre compte en cliquant ici"); - - return RedirectToAction("Index", "Home"); - } - AddErrors(result); + await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false); + + // Pour plus d'informations sur l'activation de la confirmation de compte et de la réinitialisation de mot de passe, visitez https://go.microsoft.com/fwlink/?LinkID=320771 + // Envoyer un e-mail avec ce lien + // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); + // var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); + // await UserManager.SendEmailAsync(user.Id, "Confirmer votre compte", "Confirmez votre compte en cliquant ici"); + + return RedirectToAction("Index", "Home"); } + AddErrors(result); + } - // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire - return View(model); + // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire + return View(model); + } + + // + // GET: /Account/ConfirmEmail + [AllowAnonymous] + public async Task ConfirmEmail(string userId, string code) + { + if (userId == null || code == null) + { + return View("Error"); } + var result = await UserManager.ConfirmEmailAsync(userId, code); + return View(result.Succeeded ? "ConfirmEmail" : "Error"); + } + + // + // GET: /Account/ForgotPassword + [AllowAnonymous] + public ActionResult ForgotPassword() + { + return View(); + } - // - // GET: /Account/ConfirmEmail - [AllowAnonymous] - public async Task ConfirmEmail(string userId, string code) + // + // POST: /Account/ForgotPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ForgotPassword(ForgotPasswordViewModel model) + { + if (ModelState.IsValid) { - if (userId == null || code == null) + var user = await UserManager.FindByNameAsync(model.Email); + if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id))) { - return View("Error"); + // Ne révélez pas que l'utilisateur n'existe pas ou qu'il n'est pas confirmé + return View("ForgotPasswordConfirmation"); } - var result = await UserManager.ConfirmEmailAsync(userId, code); - return View(result.Succeeded ? "ConfirmEmail" : "Error"); - } - // - // GET: /Account/ForgotPassword - [AllowAnonymous] - public ActionResult ForgotPassword() - { - return View(); + // Pour plus d'informations sur l'activation de la confirmation de compte et de la réinitialisation de mot de passe, visitez https://go.microsoft.com/fwlink/?LinkID=320771 + // Envoyer un e-mail avec ce lien + // string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id); + // var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); + // await UserManager.SendEmailAsync(user.Id, "Réinitialiser le mot de passe", "Réinitialisez votre mot de passe en cliquant ici"); + // return RedirectToAction("ForgotPasswordConfirmation", "Account"); } - // - // POST: /Account/ForgotPassword - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ForgotPassword(ForgotPasswordViewModel model) - { - if (ModelState.IsValid) - { - var user = await UserManager.FindByNameAsync(model.Email); - if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id))) - { - // Ne révélez pas que l'utilisateur n'existe pas ou qu'il n'est pas confirmé - return View("ForgotPasswordConfirmation"); - } + // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire + return View(model); + } - // Pour plus d'informations sur l'activation de la confirmation de compte et de la réinitialisation de mot de passe, visitez https://go.microsoft.com/fwlink/?LinkID=320771 - // Envoyer un e-mail avec ce lien - // string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id); - // var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); - // await UserManager.SendEmailAsync(user.Id, "Réinitialiser le mot de passe", "Réinitialisez votre mot de passe en cliquant ici"); - // return RedirectToAction("ForgotPasswordConfirmation", "Account"); - } + // + // GET: /Account/ForgotPasswordConfirmation + [AllowAnonymous] + public ActionResult ForgotPasswordConfirmation() + { + return View(); + } - // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire + // + // GET: /Account/ResetPassword + [AllowAnonymous] + public ActionResult ResetPassword(string code) + { + return code == null ? View("Error") : View(); + } + + // + // POST: /Account/ResetPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ResetPassword(ResetPasswordViewModel model) + { + if (!ModelState.IsValid) + { return View(model); } - - // - // GET: /Account/ForgotPasswordConfirmation - [AllowAnonymous] - public ActionResult ForgotPasswordConfirmation() + var user = await UserManager.FindByNameAsync(model.Email); + if (user == null) { - return View(); + // Ne révélez pas que l'utilisateur n'existe pas + return RedirectToAction("ResetPasswordConfirmation", "Account"); } - - // - // GET: /Account/ResetPassword - [AllowAnonymous] - public ActionResult ResetPassword(string code) + var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password); + if (result.Succeeded) { - return code == null ? View("Error") : View(); + return RedirectToAction("ResetPasswordConfirmation", "Account"); } + AddErrors(result); + return View(); + } + + // + // GET: /Account/ResetPasswordConfirmation + [AllowAnonymous] + public ActionResult ResetPasswordConfirmation() + { + return View(); + } + + // + // POST: /Account/ExternalLogin + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public ActionResult ExternalLogin(string provider, string returnUrl) + { + // Demander une redirection vers le fournisseur de connexion externe + return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl })); + } - // - // POST: /Account/ResetPassword - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ResetPassword(ResetPasswordViewModel model) + // + // GET: /Account/SendCode + [AllowAnonymous] + public async Task SendCode(string returnUrl, bool rememberMe) + { + var userId = await SignInManager.GetVerifiedUserIdAsync(); + if (userId == null) { - if (!ModelState.IsValid) - { - return View(model); - } - var user = await UserManager.FindByNameAsync(model.Email); - if (user == null) - { - // Ne révélez pas que l'utilisateur n'existe pas - return RedirectToAction("ResetPasswordConfirmation", "Account"); - } - var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password); - if (result.Succeeded) - { - return RedirectToAction("ResetPasswordConfirmation", "Account"); - } - AddErrors(result); - return View(); + return View("Error"); } + var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId); + var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); + return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } - // - // GET: /Account/ResetPasswordConfirmation - [AllowAnonymous] - public ActionResult ResetPasswordConfirmation() + // + // POST: /Account/SendCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task SendCode(SendCodeViewModel model) + { + if (!ModelState.IsValid) { return View(); } - // - // POST: /Account/ExternalLogin - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public ActionResult ExternalLogin(string provider, string returnUrl) + // Générer le jeton et l'envoyer + if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider)) { - // Demander une redirection vers le fournisseur de connexion externe - return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl })); + return View("Error"); } + return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); + } - // - // GET: /Account/SendCode - [AllowAnonymous] - public async Task SendCode(string returnUrl, bool rememberMe) + // + // GET: /Account/ExternalLoginCallback + [AllowAnonymous] + public async Task ExternalLoginCallback(string returnUrl) + { + var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); + if (loginInfo == null) { - var userId = await SignInManager.GetVerifiedUserIdAsync(); - if (userId == null) - { - return View("Error"); - } - var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId); - var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); - return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + return RedirectToAction("Login"); } - // - // POST: /Account/SendCode - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task SendCode(SendCodeViewModel model) + // Connecter cet utilisateur à ce fournisseur de connexion externe si l'utilisateur possède déjà une connexion + var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false); + switch (result) { - if (!ModelState.IsValid) - { - return View(); - } - - // Générer le jeton et l'envoyer - if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider)) - { - return View("Error"); - } - return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); + case SignInStatus.Success: + return RedirectToLocal(returnUrl); + case SignInStatus.LockedOut: + return View("Lockout"); + case SignInStatus.RequiresVerification: + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false }); + case SignInStatus.Failure: + default: + // Si l'utilisateur n'a pas de compte, invitez alors celui-ci à créer un compte + ViewBag.ReturnUrl = returnUrl; + ViewBag.LoginProvider = loginInfo.Login.LoginProvider; + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email }); } + } - // - // GET: /Account/ExternalLoginCallback - [AllowAnonymous] - public async Task ExternalLoginCallback(string returnUrl) + // + // POST: /Account/ExternalLoginConfirmation + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) + { + if (User.Identity.IsAuthenticated) { - var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); - if (loginInfo == null) - { - return RedirectToAction("Login"); - } - - // Connecter cet utilisateur à ce fournisseur de connexion externe si l'utilisateur possède déjà une connexion - var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false); - switch (result) - { - case SignInStatus.Success: - return RedirectToLocal(returnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - case SignInStatus.RequiresVerification: - return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false }); - case SignInStatus.Failure: - default: - // Si l'utilisateur n'a pas de compte, invitez alors celui-ci à créer un compte - ViewBag.ReturnUrl = returnUrl; - ViewBag.LoginProvider = loginInfo.Login.LoginProvider; - return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email }); - } + return RedirectToAction("Index", "Manage"); } - // - // POST: /Account/ExternalLoginConfirmation - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) + if (ModelState.IsValid) { - if (User.Identity.IsAuthenticated) + // Obtenir des informations sur l'utilisateur à partir du fournisseur de connexion externe + var info = await AuthenticationManager.GetExternalLoginInfoAsync(); + if (info == null) { - return RedirectToAction("Index", "Manage"); + return View("ExternalLoginFailure"); } - - if (ModelState.IsValid) + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await UserManager.CreateAsync(user); + if (result.Succeeded) { - // Obtenir des informations sur l'utilisateur à partir du fournisseur de connexion externe - var info = await AuthenticationManager.GetExternalLoginInfoAsync(); - if (info == null) - { - return View("ExternalLoginFailure"); - } - var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; - var result = await UserManager.CreateAsync(user); + result = await UserManager.AddLoginAsync(user.Id, info.Login); if (result.Succeeded) { - result = await UserManager.AddLoginAsync(user.Id, info.Login); - if (result.Succeeded) - { - await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); - return RedirectToLocal(returnUrl); - } + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); + return RedirectToLocal(returnUrl); } - AddErrors(result); } - - ViewBag.ReturnUrl = returnUrl; - return View(model); + AddErrors(result); } - // - // POST: /Account/LogOff - [HttpPost] - [ValidateAntiForgeryToken] - public ActionResult LogOff() - { - AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); - return RedirectToAction("Index", "Home"); - } + ViewBag.ReturnUrl = returnUrl; + return View(model); + } - // - // GET: /Account/ExternalLoginFailure - [AllowAnonymous] - public ActionResult ExternalLoginFailure() - { - return View(); - } + // + // POST: /Account/LogOff + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult LogOff() + { + AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); + return RedirectToAction("Index", "Home"); + } + + // + // GET: /Account/ExternalLoginFailure + [AllowAnonymous] + public ActionResult ExternalLoginFailure() + { + return View(); + } - protected override void Dispose(bool disposing) + protected override void Dispose(bool disposing) + { + if (disposing) { - if (disposing) + if (_userManager != null) { - if (_userManager != null) - { - _userManager.Dispose(); - _userManager = null; - } - - if (_signInManager != null) - { - _signInManager.Dispose(); - _signInManager = null; - } + _userManager.Dispose(); + _userManager = null; } - base.Dispose(disposing); + if (_signInManager != null) + { + _signInManager.Dispose(); + _signInManager = null; + } } - #region Applications auxiliaires - // Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes - private const string XsrfKey = "XsrfId"; + base.Dispose(disposing); + } - private IAuthenticationManager AuthenticationManager + #region Applications auxiliaires + // Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes + private const string XsrfKey = "XsrfId"; + + private IAuthenticationManager AuthenticationManager + { + get { - get - { - return HttpContext.GetOwinContext().Authentication; - } + return HttpContext.GetOwinContext().Authentication; } + } - private void AddErrors(IdentityResult result) + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) { - foreach (var error in result.Errors) - { - ModelState.AddModelError("", error); - } + ModelState.AddModelError("", error); } + } - private ActionResult RedirectToLocal(string returnUrl) + private ActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) { - if (Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - return RedirectToAction("Index", "Home"); + return Redirect(returnUrl); } + return RedirectToAction("Index", "Home"); + } - internal class ChallengeResult : HttpUnauthorizedResult + internal class ChallengeResult : HttpUnauthorizedResult + { + public ChallengeResult(string provider, string redirectUri) + : this(provider, redirectUri, null) { - public ChallengeResult(string provider, string redirectUri) - : this(provider, redirectUri, null) - { - } + } - public ChallengeResult(string provider, string redirectUri, string userId) - { - LoginProvider = provider; - RedirectUri = redirectUri; - UserId = userId; - } + public ChallengeResult(string provider, string redirectUri, string userId) + { + LoginProvider = provider; + RedirectUri = redirectUri; + UserId = userId; + } - public string LoginProvider { get; set; } - public string RedirectUri { get; set; } - public string UserId { get; set; } + public string LoginProvider { get; set; } + public string RedirectUri { get; set; } + public string UserId { get; set; } - public override void ExecuteResult(ControllerContext context) + public override void ExecuteResult(ControllerContext context) + { + var properties = new AuthenticationProperties { RedirectUri = RedirectUri }; + if (UserId != null) { - var properties = new AuthenticationProperties { RedirectUri = RedirectUri }; - if (UserId != null) - { - properties.Dictionary[XsrfKey] = UserId; - } - context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); + properties.Dictionary[XsrfKey] = UserId; } + context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); } - #endregion } + #endregion } \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs index 1b4bdd45..58fc4861 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs @@ -9,87 +9,86 @@ using Microsoft.Owin.Security; using OpenIddict.Client.Owin; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Sandbox.AspNet.Server.Controllers +namespace OpenIddict.Sandbox.AspNet.Server.Controllers; + +public class AuthenticationController : Controller { - public class AuthenticationController : Controller + // Note: this controller uses the same callback action for all providers + // but for users who prefer using a different action per provider, + // the following action can be split into separate actions. + [AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")] + public async Task LogInCallback() { - // Note: this controller uses the same callback action for all providers - // but for users who prefer using a different action per provider, - // the following action can be split into separate actions. - [AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")] - public async Task LogInCallback() - { - var context = HttpContext.GetOwinContext(); + var context = HttpContext.GetOwinContext(); - // Retrieve the authorization data validated by OpenIddict as part of the callback handling. - var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); + // Retrieve the authorization data validated by OpenIddict as part of the callback handling. + var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); - // Multiple strategies exist to handle OAuth 2.0/OpenID Connect callbacks, each with their pros and cons: - // - // * Directly using the tokens to perform the necessary action(s) on behalf of the user, which is suitable - // for applications that don't need a long-term access to the user's resources or don't want to store - // access/refresh tokens in a database or in an authentication cookie (which has security implications). - // It is also suitable for applications that don't need to authenticate users but only need to perform - // action(s) on their behalf by making API calls using the access token returned by the remote server. - // - // * Storing the external claims/tokens in a database (and optionally keeping the essential claims in an - // authentication cookie so that cookie size limits are not hit). For the applications that use ASP.NET - // Core Identity, the UserManager.SetAuthenticationTokenAsync() API can be used to store external tokens. - // - // Note: in this case, it's recommended to use column encryption to protect the tokens in the database. + // Multiple strategies exist to handle OAuth 2.0/OpenID Connect callbacks, each with their pros and cons: + // + // * Directly using the tokens to perform the necessary action(s) on behalf of the user, which is suitable + // for applications that don't need a long-term access to the user's resources or don't want to store + // access/refresh tokens in a database or in an authentication cookie (which has security implications). + // It is also suitable for applications that don't need to authenticate users but only need to perform + // action(s) on their behalf by making API calls using the access token returned by the remote server. + // + // * Storing the external claims/tokens in a database (and optionally keeping the essential claims in an + // authentication cookie so that cookie size limits are not hit). For the applications that use ASP.NET + // Core Identity, the UserManager.SetAuthenticationTokenAsync() API can be used to store external tokens. + // + // Note: in this case, it's recommended to use column encryption to protect the tokens in the database. + // + // * Storing the external claims/tokens in an authentication cookie, which doesn't require having + // a user database but may be affected by the cookie size limits enforced by most browser vendors + // (e.g Safari for macOS and Safari for iOS/iPadOS enforce a per-domain 4KB limit for all cookies). + // + // Note: this is the approach used here, but the external claims are first filtered to only persist + // a few claims like the user identifier. The same approach is used to store the access/refresh tokens. + + // Important: if the remote server doesn't support OpenID Connect and doesn't expose a userinfo endpoint, + // result.Principal.Identity will represent an unauthenticated identity and won't contain any user claim. + // + // Such identities cannot be used as-is to build an authentication cookie in ASP.NET (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.Identity is not ClaimsIdentity { IsAuthenticated: true }) + { + throw new InvalidOperationException("The external authorization data cannot be used for authentication."); + } + + // Build an identity based on the external claims and that will be used to create the authentication cookie. + // + // By default, all claims extracted during the authorization dance are available. The claims collection stored + // in the cookie can be filtered out or mapped to different names depending the claim name or its issuer. + var claims = result.Identity.Claims.Where(claim => claim.Type is ClaimTypes.NameIdentifier or ClaimTypes.Name // - // * Storing the external claims/tokens in an authentication cookie, which doesn't require having - // a user database but may be affected by the cookie size limits enforced by most browser vendors - // (e.g Safari for macOS and Safari for iOS/iPadOS enforce a per-domain 4KB limit for all cookies). + // Preserve the registration details to be able to resolve them later. // - // Note: this is the approach used here, but the external claims are first filtered to only persist - // a few claims like the user identifier. The same approach is used to store the access/refresh tokens. - - // Important: if the remote server doesn't support OpenID Connect and doesn't expose a userinfo endpoint, - // result.Principal.Identity will represent an unauthenticated identity and won't contain any user claim. + or Claims.Private.RegistrationId or Claims.Private.ProviderName // - // Such identities cannot be used as-is to build an authentication cookie in ASP.NET (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.Identity is not ClaimsIdentity { IsAuthenticated: true }) - { - throw new InvalidOperationException("The external authorization data cannot be used for authentication."); - } - - // Build an identity based on the external claims and that will be used to create the authentication cookie. + // The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim. // - // By default, all claims extracted during the authorization dance are available. The claims collection stored - // in the cookie can be filtered out or mapped to different names depending the claim name or its issuer. - var claims = result.Identity.Claims.Where(claim => claim.Type is ClaimTypes.NameIdentifier or ClaimTypes.Name - // - // Preserve the registration details to be able to resolve them later. - // - or Claims.Private.RegistrationId or Claims.Private.ProviderName - // - // The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim. - // - or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"); + or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"); - // Note: when using external authentication providers with ASP.NET Identity, - // the user identity MUST be added to the external authentication cookie scheme. - var identity = new ClaimsIdentity(claims, - authenticationType: DefaultAuthenticationTypes.ExternalCookie, - nameType: ClaimTypes.Name, - roleType: ClaimTypes.Role); + // Note: when using external authentication providers with ASP.NET Identity, + // the user identity MUST be added to the external authentication cookie scheme. + var identity = new ClaimsIdentity(claims, + authenticationType: DefaultAuthenticationTypes.ExternalCookie, + nameType: ClaimTypes.Name, + roleType: ClaimTypes.Role); - // Build the authentication properties based on the properties that were added when the challenge was triggered. - var properties = new AuthenticationProperties(result.Properties.Dictionary - .Where(item => item.Key is - // Preserve the return URL. - ".redirect" or + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Dictionary + .Where(item => item.Key is + // Preserve the return URL. + ".redirect" or - // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. - OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or - OpenIddictClientOwinConstants.Tokens.RefreshToken) - .ToDictionary(pair => pair.Key, pair => pair.Value)); + // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or + OpenIddictClientOwinConstants.Tokens.RefreshToken) + .ToDictionary(pair => pair.Key, pair => pair.Value)); - context.Authentication.SignIn(properties, identity); - return Redirect(properties.RedirectUri ?? "/"); - } + context.Authentication.SignIn(properties, identity); + return Redirect(properties.RedirectUri ?? "/"); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs index 82d81e8d..808f4b4d 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs @@ -24,229 +24,106 @@ using OpenIddict.Server.Owin; using Owin; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Sandbox.AspNet.Server.Controllers +namespace OpenIddict.Sandbox.AspNet.Server.Controllers; + +public class AuthorizationController : Controller { - public class AuthorizationController : Controller + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictAuthorizationManager _authorizationManager; + private readonly OpenIddictClientService _clientService; + private readonly IOpenIddictScopeManager _scopeManager; + + public AuthorizationController( + IOpenIddictApplicationManager applicationManager, + IOpenIddictAuthorizationManager authorizationManager, + OpenIddictClientService clientService, + IOpenIddictScopeManager scopeManager) { - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly OpenIddictClientService _clientService; - private readonly IOpenIddictScopeManager _scopeManager; - - public AuthorizationController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - OpenIddictClientService clientService, - IOpenIddictScopeManager scopeManager) - { - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _clientService = clientService; - _scopeManager = scopeManager; - } + _applicationManager = applicationManager; + _authorizationManager = authorizationManager; + _clientService = clientService; + _scopeManager = scopeManager; + } - [HttpGet, Route("~/connect/authorize")] - public async Task Authorize() + [HttpGet, Route("~/connect/authorize")] + public async Task Authorize() + { + var context = HttpContext.GetOwinContext(); + var request = context.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + + // Retrieve the user principal stored in the authentication cookie. + // If a max_age parameter was provided, ensure that the cookie is not too old. + // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. + var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); + if (result?.Identity == null || (request.MaxAge != null && result.Properties?.IssuedUtc != null && + DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) { - var context = HttpContext.GetOwinContext(); - var request = context.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - // Retrieve the user principal stored in the authentication cookie. - // If a max_age parameter was provided, ensure that the cookie is not too old. - // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. - var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); - if (result?.Identity == null || (request.MaxAge != null && result.Properties?.IssuedUtc != null && - DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) + // For applications that want to allow the client to select the external authentication provider + // that will be used to authenticate the user, the identity_provider parameter can be used for that. + if (!string.IsNullOrEmpty(request.IdentityProvider)) { - // For applications that want to allow the client to select the external authentication provider - // that will be used to authenticate the user, the identity_provider parameter can be used for that. - if (!string.IsNullOrEmpty(request.IdentityProvider)) + var registrations = await _clientService.GetClientRegistrationsAsync(); + if (!registrations.Any(registration => string.Equals(registration.ProviderName, + request.IdentityProvider, StringComparison.Ordinal))) { - var registrations = await _clientService.GetClientRegistrationsAsync(); - if (!registrations.Any(registration => string.Equals(registration.ProviderName, - request.IdentityProvider, StringComparison.Ordinal))) - { - context.Authentication.Challenge( - authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidRequest, - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = - "The specified identity provider is not valid." - })); - - return new EmptyResult(); - } - - var properties = new AuthenticationProperties(new Dictionary - { - // Note: when only one client is registered in the client options, - // specifying the issuer URI or the provider name is not required. - [OpenIddictClientOwinConstants.Properties.ProviderName] = request.IdentityProvider - }) - { - // Once the callback is handled, redirect the user agent to the ASP.NET Identity - // page responsible for showing the external login confirmation form if necessary. - RedirectUri = Url.Action("ExternalLoginCallback", "Account", new - { - ReturnUrl = Request.RawUrl - }) - }; - - // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. - context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType); - return new EmptyResult(); - } - - context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); - return new EmptyResult(); - } - - // Retrieve the profile of the logged in user. - var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); - - // Retrieve the application details from the database. - var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); - - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await _authorizationManager.FindAsync( - subject: user.Id, - client : await _applicationManager.GetIdAsync(application), - status : Statuses.Valid, - type : AuthorizationTypes.Permanent, - scopes : request.GetScopes()).ToListAsync(); - - switch (await _applicationManager.GetConsentTypeAsync(application)) - { - // If the consent is external (e.g when authorizations are granted by a sysadmin), - // immediately return an error if no authorization can be found in the database. - case ConsentTypes.External when authorizations.Count is 0: context.Authentication.Challenge( authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, properties: new AuthenticationProperties(new Dictionary { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidRequest, [OpenIddictServerOwinConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." - })); - - return new EmptyResult(); - - // If the consent is implicit or if an authorization was found, - // return an authorization response without displaying the consent form. - case ConsentTypes.Implicit: - case ConsentTypes.External when authorizations.Count is not 0: - case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPrompt(Prompts.Consent): - // Create the claims-based identity that will be used by OpenIddict to generate tokens. - var identity = new ClaimsIdentity( - authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, - nameType: Claims.Name, - roleType: Claims.Role); - - // Add the claims that will be persisted in the tokens. - identity.SetClaim(Claims.Subject, user.Id) - .SetClaim(Claims.Email, user.Email) - .SetClaim(Claims.Name, user.UserName) - .SetClaim(Claims.PreferredUsername, user.UserName) - .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - identity.SetScopes(request.GetScopes()); - identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); - - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - authorization ??= await _authorizationManager.CreateAsync( - identity: identity, - subject : user.Id, - client : await _applicationManager.GetIdAsync(application), - type : AuthorizationTypes.Permanent, - scopes : identity.GetScopes()); - - identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - identity.SetDestinations(GetDestinations); - - context.Authentication.SignIn(new AuthenticationProperties(), identity); - - return new EmptyResult(); - - // At this point, no authorization was found in the database and an error must be returned - // if the client application specified prompt=none in the authorization request. - case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): - case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): - context.Authentication.Challenge( - authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = - "Interactive user consent is required." + "The specified identity provider is not valid." })); return new EmptyResult(); + } - // In every other case, render the consent form. - default: return View(new AuthorizeViewModel + var properties = new AuthenticationProperties(new Dictionary { - ApplicationName = await _applicationManager.GetDisplayNameAsync(application), - Scope = request.Scope, - - // Flow the request parameters so they can be received by the Accept/Reject actions. - Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? - from name in Request.Form.AllKeys - from value in Request.Form.GetValues(name) - select new KeyValuePair(name, value) : - from name in Request.QueryString.AllKeys - from value in Request.QueryString.GetValues(name) - select new KeyValuePair(name, value) - }); + // Note: when only one client is registered in the client options, + // specifying the issuer URI or the provider name is not required. + [OpenIddictClientOwinConstants.Properties.ProviderName] = request.IdentityProvider + }) + { + // Once the callback is handled, redirect the user agent to the ASP.NET Identity + // page responsible for showing the external login confirmation form if necessary. + RedirectUri = Url.Action("ExternalLoginCallback", "Account", new + { + ReturnUrl = Request.RawUrl + }) + }; + + // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. + context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType); + return new EmptyResult(); } + + context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); + return new EmptyResult(); } - [Authorize, FormValueRequired("submit.Accept")] - [HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken] - public async Task Accept() - { - var context = HttpContext.GetOwinContext(); - var request = context.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + // Retrieve the profile of the logged in user. + var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); - // Retrieve the user principal stored in the authentication cookie. - var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); - if (result == null || result.Identity == null) - { - context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); + // Retrieve the application details from the database. + var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); - return new EmptyResult(); - } + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await _authorizationManager.FindAsync( + subject: user.Id, + client : await _applicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); - // Retrieve the profile of the logged in user. - var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); - - // Retrieve the application details from the database. - var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); - - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await _authorizationManager.FindAsync( - subject: user.Id, - client : await _applicationManager.GetIdAsync(application), - status : Statuses.Valid, - type : AuthorizationTypes.Permanent, - scopes : request.GetScopes()).ToListAsync(); - - // Note: the same check is already made in the other action but is repeated - // here to ensure a malicious user can't abuse this POST-only endpoint and - // force it to return a valid response without the external authorization. - if (authorizations.Count is 0 && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) - { + switch (await _applicationManager.GetConsentTypeAsync(application)) + { + // If the consent is external (e.g when authorizations are granted by a sysadmin), + // immediately return an error if no authorization can be found in the database. + case ConsentTypes.External when authorizations.Count is 0: context.Authentication.Challenge( authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, properties: new AuthenticationProperties(new Dictionary @@ -257,191 +134,313 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers })); return new EmptyResult(); - } - // Create the claims-based identity that will be used by OpenIddict to generate tokens. - var identity = new ClaimsIdentity( - authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, - nameType: Claims.Name, - roleType: Claims.Role); + // If the consent is implicit or if an authorization was found, + // return an authorization response without displaying the consent form. + case ConsentTypes.Implicit: + case ConsentTypes.External when authorizations.Count is not 0: + case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPrompt(Prompts.Consent): + // Create the claims-based identity that will be used by OpenIddict to generate tokens. + var identity = new ClaimsIdentity( + authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, + nameType: Claims.Name, + roleType: Claims.Role); - // Add the claims that will be persisted in the tokens. - identity.SetClaim(Claims.Subject, user.Id) - .SetClaim(Claims.Email, user.Email) - .SetClaim(Claims.Name, user.UserName) - .SetClaim(Claims.PreferredUsername, user.UserName) - .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); + // Add the claims that will be persisted in the tokens. + identity.SetClaim(Claims.Subject, user.Id) + .SetClaim(Claims.Email, user.Email) + .SetClaim(Claims.Name, user.UserName) + .SetClaim(Claims.PreferredUsername, user.UserName) + .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - identity.SetScopes(request.GetScopes()); - identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); - - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - authorization ??= await _authorizationManager.CreateAsync( - identity: identity, - subject : user.Id, - client : await _applicationManager.GetIdAsync(application), - type : AuthorizationTypes.Permanent, - scopes : identity.GetScopes()); - - identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - identity.SetDestinations(GetDestinations); + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + identity.SetScopes(request.GetScopes()); + identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + authorization ??= await _authorizationManager.CreateAsync( + identity: identity, + subject : user.Id, + client : await _applicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : identity.GetScopes()); + + identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(GetDestinations); - // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. - context.Authentication.SignIn(new AuthenticationProperties(), identity); + context.Authentication.SignIn(new AuthenticationProperties(), identity); - return new EmptyResult(); + return new EmptyResult(); + + // At this point, no authorization was found in the database and an error must be returned + // if the client application specified prompt=none in the authorization request. + case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): + case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): + context.Authentication.Challenge( + authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = + "Interactive user consent is required." + })); + + return new EmptyResult(); + + // In every other case, render the consent form. + default: return View(new AuthorizeViewModel + { + ApplicationName = await _applicationManager.GetDisplayNameAsync(application), + Scope = request.Scope, + + // Flow the request parameters so they can be received by the Accept/Reject actions. + Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? + from name in Request.Form.AllKeys + from value in Request.Form.GetValues(name) + select new KeyValuePair(name, value) : + from name in Request.QueryString.AllKeys + from value in Request.QueryString.GetValues(name) + select new KeyValuePair(name, value) + }); } + } - [Authorize, FormValueRequired("submit.Deny")] - [HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken] - // Notify OpenIddict that the authorization grant has been denied by the resource owner - // to redirect the user agent to the client application using the appropriate response_mode. - public ActionResult Deny() + [Authorize, FormValueRequired("submit.Accept")] + [HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken] + public async Task Accept() + { + var context = HttpContext.GetOwinContext(); + var request = context.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + + // Retrieve the user principal stored in the authentication cookie. + var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); + if (result == null || result.Identity == null) { - var context = HttpContext.GetOwinContext(); - context.Authentication.Challenge(OpenIddictServerOwinDefaults.AuthenticationType); + context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); return new EmptyResult(); } - [HttpGet, Route("~/connect/logout")] - public ActionResult Logout() => View(new AuthorizeViewModel - { - // Flow the request parameters so they can be received by the Accept/Reject actions. - Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? - from name in Request.Form.AllKeys - from value in Request.Form.GetValues(name) - select new KeyValuePair(name, value) : - from name in Request.QueryString.AllKeys - from value in Request.QueryString.GetValues(name) - select new KeyValuePair(name, value) - }); - - [ActionName(nameof(Logout)), HttpPost, Route("~/connect/logout"), ValidateAntiForgeryToken] - public ActionResult LogoutPost() + // Retrieve the profile of the logged in user. + var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); + + // Retrieve the application details from the database. + var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await _authorizationManager.FindAsync( + subject: user.Id, + client : await _applicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); + + // Note: the same check is already made in the other action but is repeated + // here to ensure a malicious user can't abuse this POST-only endpoint and + // force it to return a valid response without the external authorization. + if (authorizations.Count is 0 && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) { - var context = HttpContext.GetOwinContext(); - context.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie); - - context.Authentication.SignOut( + context.Authentication.Challenge( authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties + properties: new AuthenticationProperties(new Dictionary { - RedirectUri = "/" - }); + [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); return new EmptyResult(); } - [HttpPost, Route("~/connect/token")] - public async Task Exchange() - { - var context = HttpContext.GetOwinContext(); - var request = context.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + // Create the claims-based identity that will be used by OpenIddict to generate tokens. + var identity = new ClaimsIdentity( + authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, + nameType: Claims.Name, + roleType: Claims.Role); + + // Add the claims that will be persisted in the tokens. + identity.SetClaim(Claims.Subject, user.Id) + .SetClaim(Claims.Email, user.Email) + .SetClaim(Claims.Name, user.UserName) + .SetClaim(Claims.PreferredUsername, user.UserName) + .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); + + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + identity.SetScopes(request.GetScopes()); + identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + authorization ??= await _authorizationManager.CreateAsync( + identity: identity, + subject : user.Id, + client : await _applicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : identity.GetScopes()); + + identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(GetDestinations); + + // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. + context.Authentication.SignIn(new AuthenticationProperties(), identity); + + return new EmptyResult(); + } - if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) - { - // Retrieve the claims identity stored in the authorization code/device code/refresh token. - var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); + [Authorize, FormValueRequired("submit.Deny")] + [HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken] + // Notify OpenIddict that the authorization grant has been denied by the resource owner + // to redirect the user agent to the client application using the appropriate response_mode. + public ActionResult Deny() + { + var context = HttpContext.GetOwinContext(); + context.Authentication.Challenge(OpenIddictServerOwinDefaults.AuthenticationType); - // Retrieve the user profile corresponding to the authorization code/refresh token. - var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetClaim(Claims.Subject)); - if (user == null) - { - context.Authentication.Challenge( - authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The token is no longer valid." - })); + return new EmptyResult(); + } - return new EmptyResult(); - } + [HttpGet, Route("~/connect/logout")] + public ActionResult Logout() => View(new AuthorizeViewModel + { + // Flow the request parameters so they can be received by the Accept/Reject actions. + Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? + from name in Request.Form.AllKeys + from value in Request.Form.GetValues(name) + select new KeyValuePair(name, value) : + from name in Request.QueryString.AllKeys + from value in Request.QueryString.GetValues(name) + select new KeyValuePair(name, value) + }); + + [ActionName(nameof(Logout)), HttpPost, Route("~/connect/logout"), ValidateAntiForgeryToken] + public ActionResult LogoutPost() + { + var context = HttpContext.GetOwinContext(); + context.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie); - // Ensure the user is still allowed to sign in. - if (context.GetUserManager().IsLockedOut(user.Id)) - { - context.Authentication.Challenge( - authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." - })); + context.Authentication.SignOut( + authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties + { + RedirectUri = "/" + }); - return new EmptyResult(); - } + return new EmptyResult(); + } - var identity = new ClaimsIdentity(result.Identity.Claims, - authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, - nameType: Claims.Name, - roleType: Claims.Role); + [HttpPost, Route("~/connect/token")] + public async Task Exchange() + { + var context = HttpContext.GetOwinContext(); + var request = context.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - // Override the user claims present in the principal in case they - // changed since the authorization code/refresh token was issued. - identity.SetClaim(Claims.Subject, user.Id) - .SetClaim(Claims.Email, user.Email) - .SetClaim(Claims.Name, user.UserName) - .SetClaim(Claims.PreferredUsername, user.UserName) - .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); + if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) + { + // Retrieve the claims identity stored in the authorization code/device code/refresh token. + var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); - identity.SetDestinations(GetDestinations); + // Retrieve the user profile corresponding to the authorization code/refresh token. + var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetClaim(Claims.Subject)); + if (user == null) + { + context.Authentication.Challenge( + authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant, + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The token is no longer valid." + })); - // Ask OpenIddict to issue the appropriate access/identity tokens. - context.Authentication.SignIn(new AuthenticationProperties(), identity); + return new EmptyResult(); + } + + // Ensure the user is still allowed to sign in. + if (context.GetUserManager().IsLockedOut(user.Id)) + { + context.Authentication.Challenge( + authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant, + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." + })); return new EmptyResult(); } - throw new InvalidOperationException("The specified grant type is not supported."); + var identity = new ClaimsIdentity(result.Identity.Claims, + authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, + nameType: Claims.Name, + roleType: Claims.Role); + + // Override the user claims present in the principal in case they + // changed since the authorization code/refresh token was issued. + identity.SetClaim(Claims.Subject, user.Id) + .SetClaim(Claims.Email, user.Email) + .SetClaim(Claims.Name, user.UserName) + .SetClaim(Claims.PreferredUsername, user.UserName) + .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); + + identity.SetDestinations(GetDestinations); + + // Ask OpenIddict to issue the appropriate access/identity tokens. + context.Authentication.SignIn(new AuthenticationProperties(), identity); + + return new EmptyResult(); } - private static IEnumerable GetDestinations(Claim claim) - { - // Note: by default, claims are NOT automatically included in the access and identity tokens. - // To allow OpenIddict to serialize them, you must attach them a destination, that specifies - // whether they should be included in access tokens, in identity tokens or in both. + throw new InvalidOperationException("The specified grant type is not supported."); + } - switch (claim.Type) - { - case Claims.Name or Claims.PreferredUsername: - yield return Destinations.AccessToken; + private static IEnumerable GetDestinations(Claim claim) + { + // Note: by default, claims are NOT automatically included in the access and identity tokens. + // To allow OpenIddict to serialize them, you must attach them a destination, that specifies + // whether they should be included in access tokens, in identity tokens or in both. - if (claim.Subject.HasScope(Scopes.Profile)) - yield return Destinations.IdentityToken; + switch (claim.Type) + { + case Claims.Name or Claims.PreferredUsername: + yield return Destinations.AccessToken; - yield break; + if (claim.Subject.HasScope(Scopes.Profile)) + yield return Destinations.IdentityToken; - case Claims.Email: - yield return Destinations.AccessToken; + yield break; - if (claim.Subject.HasScope(Scopes.Email)) - yield return Destinations.IdentityToken; + case Claims.Email: + yield return Destinations.AccessToken; - yield break; + if (claim.Subject.HasScope(Scopes.Email)) + yield return Destinations.IdentityToken; - case Claims.Role: - yield return Destinations.AccessToken; + yield break; - if (claim.Subject.HasScope(Scopes.Roles)) - yield return Destinations.IdentityToken; + case Claims.Role: + yield return Destinations.AccessToken; - yield break; + if (claim.Subject.HasScope(Scopes.Roles)) + yield return Destinations.IdentityToken; - // Never include the security stamp in the access and identity tokens, as it's a secret value. - case "AspNet.Identity.SecurityStamp": yield break; + yield break; - default: - yield return Destinations.AccessToken; - yield break; - } + // Never include the security stamp in the access and identity tokens, as it's a secret value. + case "AspNet.Identity.SecurityStamp": yield break; + + default: + yield return Destinations.AccessToken; + yield break; } } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/HomeController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/HomeController.cs index 08897138..85392783 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/HomeController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/HomeController.cs @@ -1,26 +1,25 @@ using System.Web.Mvc; -namespace OpenIddict.Sandbox.AspNet.Server.Controllers +namespace OpenIddict.Sandbox.AspNet.Server.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + public ActionResult Index() { - public ActionResult Index() - { - return View(); - } + return View(); + } - public ActionResult About() - { - ViewBag.Message = "Your application description page."; + public ActionResult About() + { + ViewBag.Message = "Your application description page."; - return View(); - } + return View(); + } - public ActionResult Contact() - { - ViewBag.Message = "Your contact page."; + public ActionResult Contact() + { + ViewBag.Message = "Your contact page."; - return View(); - } + return View(); } } \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ManageController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ManageController.cs index af0c2326..d375429b 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ManageController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ManageController.cs @@ -7,229 +7,258 @@ using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; using OpenIddict.Sandbox.AspNet.Server.Models; -namespace OpenIddict.Sandbox.AspNet.Server.Controllers +namespace OpenIddict.Sandbox.AspNet.Server.Controllers; + +[Authorize] +public class ManageController : Controller { - [Authorize] - public class ManageController : Controller + private ApplicationSignInManager _signInManager; + private ApplicationUserManager _userManager; + + public ManageController() + { + } + + public ManageController(ApplicationUserManager userManager, ApplicationSignInManager signInManager) { - private ApplicationSignInManager _signInManager; - private ApplicationUserManager _userManager; + UserManager = userManager; + SignInManager = signInManager; + } - public ManageController() + public ApplicationSignInManager SignInManager + { + get { + return _signInManager ?? HttpContext.GetOwinContext().Get(); + } + private set + { + _signInManager = value; } + } - public ManageController(ApplicationUserManager userManager, ApplicationSignInManager signInManager) + public ApplicationUserManager UserManager + { + get { - UserManager = userManager; - SignInManager = signInManager; + return _userManager ?? HttpContext.GetOwinContext().GetUserManager(); } - - public ApplicationSignInManager SignInManager + private set { - get - { - return _signInManager ?? HttpContext.GetOwinContext().Get(); - } - private set - { - _signInManager = value; - } + _userManager = value; } + } - public ApplicationUserManager UserManager + // + // GET: /Manage/Index + public async Task Index(ManageMessageId? message) + { + ViewBag.StatusMessage = + message == ManageMessageId.ChangePasswordSuccess ? "Votre mot de passe a été changé." + : message == ManageMessageId.SetPasswordSuccess ? "Votre mot de passe a été défini." + : message == ManageMessageId.SetTwoFactorSuccess ? "Votre fournisseur d'authentification à 2 facteurs a été défini." + : message == ManageMessageId.Error ? "Une erreur s'est produite." + : message == ManageMessageId.AddPhoneSuccess ? "Votre numéro de téléphone a été ajouté." + : message == ManageMessageId.RemovePhoneSuccess ? "Votre numéro de téléphone a été supprimé." + : ""; + + var userId = User.Identity.GetUserId(); + var model = new IndexViewModel { - get - { - return _userManager ?? HttpContext.GetOwinContext().GetUserManager(); - } - private set + HasPassword = HasPassword(), + PhoneNumber = await UserManager.GetPhoneNumberAsync(userId), + TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId), + Logins = await UserManager.GetLoginsAsync(userId), + BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId) + }; + return View(model); + } + + // + // POST: /Manage/RemoveLogin + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemoveLogin(string loginProvider, string providerKey) + { + ManageMessageId? message; + var result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey)); + if (result.Succeeded) + { + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user != null) { - _userManager = value; + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } + message = ManageMessageId.RemoveLoginSuccess; + } + else + { + message = ManageMessageId.Error; } + return RedirectToAction("ManageLogins", new { Message = message }); + } - // - // GET: /Manage/Index - public async Task Index(ManageMessageId? message) + // + // GET: /Manage/AddPhoneNumber + public ActionResult AddPhoneNumber() + { + return View(); + } + + // + // POST: /Manage/AddPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddPhoneNumber(AddPhoneNumberViewModel model) + { + if (!ModelState.IsValid) { - ViewBag.StatusMessage = - message == ManageMessageId.ChangePasswordSuccess ? "Votre mot de passe a été changé." - : message == ManageMessageId.SetPasswordSuccess ? "Votre mot de passe a été défini." - : message == ManageMessageId.SetTwoFactorSuccess ? "Votre fournisseur d'authentification à 2 facteurs a été défini." - : message == ManageMessageId.Error ? "Une erreur s'est produite." - : message == ManageMessageId.AddPhoneSuccess ? "Votre numéro de téléphone a été ajouté." - : message == ManageMessageId.RemovePhoneSuccess ? "Votre numéro de téléphone a été supprimé." - : ""; - - var userId = User.Identity.GetUserId(); - var model = new IndexViewModel - { - HasPassword = HasPassword(), - PhoneNumber = await UserManager.GetPhoneNumberAsync(userId), - TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId), - Logins = await UserManager.GetLoginsAsync(userId), - BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId) - }; return View(model); } - - // - // POST: /Manage/RemoveLogin - [HttpPost] - [ValidateAntiForgeryToken] - public async Task RemoveLogin(string loginProvider, string providerKey) + // Générer le jeton et l'envoyer + var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), model.Number); + if (UserManager.SmsService != null) { - ManageMessageId? message; - var result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey)); - if (result.Succeeded) - { - var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - if (user != null) - { - await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); - } - message = ManageMessageId.RemoveLoginSuccess; - } - else + var message = new IdentityMessage { - message = ManageMessageId.Error; - } - return RedirectToAction("ManageLogins", new { Message = message }); + Destination = model.Number, + Body = "Votre code de sécurité est : " + code + }; + await UserManager.SmsService.SendAsync(message); } + return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number }); + } - // - // GET: /Manage/AddPhoneNumber - public ActionResult AddPhoneNumber() + // + // POST: /Manage/EnableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EnableTwoFactorAuthentication() + { + await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true); + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user != null) { - return View(); + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } + return RedirectToAction("Index", "Manage"); + } - // - // POST: /Manage/AddPhoneNumber - [HttpPost] - [ValidateAntiForgeryToken] - public async Task AddPhoneNumber(AddPhoneNumberViewModel model) + // + // POST: /Manage/DisableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DisableTwoFactorAuthentication() + { + await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false); + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user != null) { - if (!ModelState.IsValid) - { - return View(model); - } - // Générer le jeton et l'envoyer - var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), model.Number); - if (UserManager.SmsService != null) - { - var message = new IdentityMessage - { - Destination = model.Number, - Body = "Votre code de sécurité est : " + code - }; - await UserManager.SmsService.SendAsync(message); - } - return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number }); + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } + return RedirectToAction("Index", "Manage"); + } + + // + // GET: /Manage/VerifyPhoneNumber + public async Task VerifyPhoneNumber(string phoneNumber) + { + var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), phoneNumber); + // Envoyer un SMS via le fournisseur SMS afin de vérifier le numéro de téléphone + return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); + } - // - // POST: /Manage/EnableTwoFactorAuthentication - [HttpPost] - [ValidateAntiForgeryToken] - public async Task EnableTwoFactorAuthentication() + // + // POST: /Manage/VerifyPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) + { + if (!ModelState.IsValid) { - await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true); - var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - if (user != null) - { - await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); - } - return RedirectToAction("Index", "Manage"); + return View(model); } - - // - // POST: /Manage/DisableTwoFactorAuthentication - [HttpPost] - [ValidateAntiForgeryToken] - public async Task DisableTwoFactorAuthentication() + var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code); + if (result.Succeeded) { - await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false); var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); if (user != null) { await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } - return RedirectToAction("Index", "Manage"); + return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess }); } + // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire + ModelState.AddModelError("", "La vérification du téléphone a échoué"); + return View(model); + } - // - // GET: /Manage/VerifyPhoneNumber - public async Task VerifyPhoneNumber(string phoneNumber) + // + // POST: /Manage/RemovePhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemovePhoneNumber() + { + var result = await UserManager.SetPhoneNumberAsync(User.Identity.GetUserId(), null); + if (!result.Succeeded) + { + return RedirectToAction("Index", new { Message = ManageMessageId.Error }); + } + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user != null) { - var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), phoneNumber); - // Envoyer un SMS via le fournisseur SMS afin de vérifier le numéro de téléphone - return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); + await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } + return RedirectToAction("Index", new { Message = ManageMessageId.RemovePhoneSuccess }); + } - // - // POST: /Manage/VerifyPhoneNumber - [HttpPost] - [ValidateAntiForgeryToken] - public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) + // + // GET: /Manage/ChangePassword + public ActionResult ChangePassword() + { + return View(); + } + + // + // POST: /Manage/ChangePassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ChangePassword(ChangePasswordViewModel model) + { + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } - var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code); - if (result.Succeeded) - { - var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - if (user != null) - { - await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); - } - return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess }); - } - // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire - ModelState.AddModelError("", "La vérification du téléphone a échoué"); return View(model); } - - // - // POST: /Manage/RemovePhoneNumber - [HttpPost] - [ValidateAntiForgeryToken] - public async Task RemovePhoneNumber() + var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword); + if (result.Succeeded) { - var result = await UserManager.SetPhoneNumberAsync(User.Identity.GetUserId(), null); - if (!result.Succeeded) - { - return RedirectToAction("Index", new { Message = ManageMessageId.Error }); - } var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); if (user != null) { await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } - return RedirectToAction("Index", new { Message = ManageMessageId.RemovePhoneSuccess }); + return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess }); } + AddErrors(result); + return View(model); + } - // - // GET: /Manage/ChangePassword - public ActionResult ChangePassword() - { - return View(); - } + // + // GET: /Manage/SetPassword + public ActionResult SetPassword() + { + return View(); + } - // - // POST: /Manage/ChangePassword - [HttpPost] - [ValidateAntiForgeryToken] - public async Task ChangePassword(ChangePasswordViewModel model) + // + // POST: /Manage/SetPassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task SetPassword(SetPasswordViewModel model) + { + if (ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } - var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword); + var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword); if (result.Succeeded) { var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); @@ -237,152 +266,122 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers { await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); } - return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess }); + return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess }); } AddErrors(result); - return View(model); } - // - // GET: /Manage/SetPassword - public ActionResult SetPassword() - { - return View(); - } + // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire + return View(model); + } - // - // POST: /Manage/SetPassword - [HttpPost] - [ValidateAntiForgeryToken] - public async Task SetPassword(SetPasswordViewModel model) + // + // GET: /Manage/ManageLogins + public async Task ManageLogins(ManageMessageId? message) + { + ViewBag.StatusMessage = + message == ManageMessageId.RemoveLoginSuccess ? "La connexion externe a été supprimée." + : message == ManageMessageId.Error ? "Une erreur s'est produite." + : ""; + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user == null) { - if (ModelState.IsValid) - { - var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword); - if (result.Succeeded) - { - var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - if (user != null) - { - await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); - } - return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess }); - } - AddErrors(result); - } - - // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire - return View(model); + return View("Error"); } - - // - // GET: /Manage/ManageLogins - public async Task ManageLogins(ManageMessageId? message) + var userLogins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()); + var otherLogins = AuthenticationManager.GetExternalAuthenticationTypes().Where(auth => userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList(); + ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1; + return View(new ManageLoginsViewModel { - ViewBag.StatusMessage = - message == ManageMessageId.RemoveLoginSuccess ? "La connexion externe a été supprimée." - : message == ManageMessageId.Error ? "Une erreur s'est produite." - : ""; - var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); - if (user == null) - { - return View("Error"); - } - var userLogins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()); - var otherLogins = AuthenticationManager.GetExternalAuthenticationTypes().Where(auth => userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList(); - ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1; - return View(new ManageLoginsViewModel - { - CurrentLogins = userLogins, - OtherLogins = otherLogins - }); - } + CurrentLogins = userLogins, + OtherLogins = otherLogins + }); + } - // - // POST: /Manage/LinkLogin - [HttpPost] - [ValidateAntiForgeryToken] - public ActionResult LinkLogin(string provider) - { - // Demander une redirection vers le fournisseur de connexion externe afin de lier une connexion pour l'utilisateur actuel - return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId()); - } + // + // POST: /Manage/LinkLogin + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult LinkLogin(string provider) + { + // Demander une redirection vers le fournisseur de connexion externe afin de lier une connexion pour l'utilisateur actuel + return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId()); + } - // - // GET: /Manage/LinkLoginCallback - public async Task LinkLoginCallback() + // + // GET: /Manage/LinkLoginCallback + public async Task LinkLoginCallback() + { + var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); + if (loginInfo == null) { - var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); - if (loginInfo == null) - { - return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); - } - var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login); - return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); + return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); } + var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login); + return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); + } - protected override void Dispose(bool disposing) + protected override void Dispose(bool disposing) + { + if (disposing && _userManager != null) { - if (disposing && _userManager != null) - { - _userManager.Dispose(); - _userManager = null; - } - - base.Dispose(disposing); + _userManager.Dispose(); + _userManager = null; } + base.Dispose(disposing); + } + #region Applications d'assistance - // Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes - private const string XsrfKey = "XsrfId"; + // Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes + private const string XsrfKey = "XsrfId"; - private IAuthenticationManager AuthenticationManager + private IAuthenticationManager AuthenticationManager + { + get { - get - { - return HttpContext.GetOwinContext().Authentication; - } + return HttpContext.GetOwinContext().Authentication; } + } - private void AddErrors(IdentityResult result) + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) { - foreach (var error in result.Errors) - { - ModelState.AddModelError("", error); - } + ModelState.AddModelError("", error); } + } - private bool HasPassword() + private bool HasPassword() + { + var user = UserManager.FindById(User.Identity.GetUserId()); + if (user != null) { - var user = UserManager.FindById(User.Identity.GetUserId()); - if (user != null) - { - return user.PasswordHash != null; - } - return false; + return user.PasswordHash != null; } + return false; + } - private bool HasPhoneNumber() + private bool HasPhoneNumber() + { + var user = UserManager.FindById(User.Identity.GetUserId()); + if (user != null) { - var user = UserManager.FindById(User.Identity.GetUserId()); - if (user != null) - { - return user.PhoneNumber != null; - } - return false; + return user.PhoneNumber != null; } + return false; + } - public enum ManageMessageId - { - AddPhoneSuccess, - ChangePasswordSuccess, - SetTwoFactorSuccess, - SetPasswordSuccess, - RemoveLoginSuccess, - RemovePhoneSuccess, - Error - } + public enum ManageMessageId + { + AddPhoneSuccess, + ChangePasswordSuccess, + SetTwoFactorSuccess, + SetPasswordSuccess, + RemoveLoginSuccess, + RemovePhoneSuccess, + Error + } #endregion - } } \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs index 1ed4d811..80318ef7 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs @@ -10,52 +10,51 @@ using OpenIddict.Abstractions; using OpenIddict.Validation.Owin; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Sandbox.AspNet.Server.Controllers +namespace OpenIddict.Sandbox.AspNet.Server.Controllers; + +[HostAuthentication(OpenIddictValidationOwinDefaults.AuthenticationType)] +public class ResourceController : ApiController { - [HostAuthentication(OpenIddictValidationOwinDefaults.AuthenticationType)] - public class ResourceController : ApiController + [Authorize, HttpGet, Route("~/api/message")] + public async Task GetMessage() { - [Authorize, HttpGet, Route("~/api/message")] - public async Task GetMessage() - { - var context = Request.GetOwinContext(); + var context = Request.GetOwinContext(); - // This demo action requires that the client application be granted the "demo_api" scope. - // If it was not granted, a detailed error is returned to the client application to inform it - // that the authorization process must be restarted with the specified scope to access this API. - if (User is not ClaimsPrincipal principal || !principal.HasScope("demo_api")) - { - context.Authentication.Challenge( - authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictValidationOwinConstants.Properties.Scope] = "demo_api", - [OpenIddictValidationOwinConstants.Properties.Error] = Errors.InsufficientScope, - [OpenIddictValidationOwinConstants.Properties.ErrorDescription] = - "The 'demo_api' scope is required to perform this action." - })); - return Unauthorized(); - } - - var user = await context.GetUserManager().FindByIdAsync( - ((ClaimsPrincipal) User).FindFirst(Claims.Subject).Value); - if (user is null) - { - context.Authentication.Challenge( - authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictValidationOwinConstants.Properties.Error] = Errors.InvalidToken, - [OpenIddictValidationOwinConstants.Properties.ErrorDescription] = - "The specified access token is bound to an account that no longer exists." - })); - return Unauthorized(); - } + // This demo action requires that the client application be granted the "demo_api" scope. + // If it was not granted, a detailed error is returned to the client application to inform it + // that the authorization process must be restarted with the specified scope to access this API. + if (User is not ClaimsPrincipal principal || !principal.HasScope("demo_api")) + { + context.Authentication.Challenge( + authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictValidationOwinConstants.Properties.Scope] = "demo_api", + [OpenIddictValidationOwinConstants.Properties.Error] = Errors.InsufficientScope, + [OpenIddictValidationOwinConstants.Properties.ErrorDescription] = + "The 'demo_api' scope is required to perform this action." + })); + return Unauthorized(); + } - return ResponseMessage(new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent($"{user.UserName} has been successfully authenticated.") - }); + var user = await context.GetUserManager().FindByIdAsync( + ((ClaimsPrincipal) User).FindFirst(Claims.Subject).Value); + if (user is null) + { + context.Authentication.Challenge( + authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictValidationOwinConstants.Properties.Error] = Errors.InvalidToken, + [OpenIddictValidationOwinConstants.Properties.ErrorDescription] = + "The specified access token is bound to an account that no longer exists." + })); + return Unauthorized(); } + + return ResponseMessage(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent($"{user.UserName} has been successfully authenticated.") + }); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Global.asax.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Global.asax.cs index 5d3c0578..b4ea1885 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Global.asax.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Global.asax.cs @@ -3,16 +3,15 @@ using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; -namespace OpenIddict.Sandbox.AspNet.Server +namespace OpenIddict.Sandbox.AspNet.Server; + +public class MvcApplication : HttpApplication { - public class MvcApplication : HttpApplication + protected void Application_Start() { - protected void Application_Start() - { - AreaRegistration.RegisterAllAreas(); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); - RouteConfig.RegisterRoutes(RouteTable.Routes); - BundleConfig.RegisterBundles(BundleTable.Bundles); - } + AreaRegistration.RegisterAllAreas(); + FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + RouteConfig.RegisterRoutes(RouteTable.Routes); + BundleConfig.RegisterBundles(BundleTable.Bundles); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/AsyncEnumerableExtensions.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/AsyncEnumerableExtensions.cs index ae5169e4..385d9034 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/AsyncEnumerableExtensions.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/AsyncEnumerableExtensions.cs @@ -2,30 +2,29 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace OpenIddict.Sandbox.AspNet.Server.Helpers +namespace OpenIddict.Sandbox.AspNet.Server.Helpers; + +public static class AsyncEnumerableExtensions { - public static class AsyncEnumerableExtensions + public static Task> ToListAsync(this IAsyncEnumerable source) { - public static Task> ToListAsync(this IAsyncEnumerable source) + if (source == null) { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } + throw new ArgumentNullException(nameof(source)); + } - return ExecuteAsync(); + return ExecuteAsync(); - async Task> ExecuteAsync() - { - var list = new List(); - - await foreach (var element in source) - { - list.Add(element); - } + async Task> ExecuteAsync() + { + var list = new List(); - return list; + await foreach (var element in source) + { + list.Add(element); } + + return list; } } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/FormValueRequiredAttribute.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/FormValueRequiredAttribute.cs index 41372694..82fefb9f 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/FormValueRequiredAttribute.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/FormValueRequiredAttribute.cs @@ -2,38 +2,37 @@ using System; using System.Reflection; using System.Web.Mvc; -namespace OpenIddict.Sandbox.AspNet.Server.Helpers +namespace OpenIddict.Sandbox.AspNet.Server.Helpers; + +public sealed class FormValueRequiredAttribute : ActionMethodSelectorAttribute { - public sealed class FormValueRequiredAttribute : ActionMethodSelectorAttribute + private readonly string _name; + + public FormValueRequiredAttribute(string name) { - private readonly string _name; + _name = name; + } - public FormValueRequiredAttribute(string name) + public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) + { + if (string.Equals(controllerContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase) || + string.Equals(controllerContext.HttpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase) || + string.Equals(controllerContext.HttpContext.Request.HttpMethod, "DELETE", StringComparison.OrdinalIgnoreCase) || + string.Equals(controllerContext.HttpContext.Request.HttpMethod, "TRACE", StringComparison.OrdinalIgnoreCase)) { - _name = name; + return false; } - public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) + if (string.IsNullOrEmpty(controllerContext.HttpContext.Request.ContentType)) { - if (string.Equals(controllerContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase) || - string.Equals(controllerContext.HttpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase) || - string.Equals(controllerContext.HttpContext.Request.HttpMethod, "DELETE", StringComparison.OrdinalIgnoreCase) || - string.Equals(controllerContext.HttpContext.Request.HttpMethod, "TRACE", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.IsNullOrEmpty(controllerContext.HttpContext.Request.ContentType)) - { - return false; - } - - if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - return false; - } + return false; + } - return !string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_name]); + if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) + { + return false; } + + return !string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_name]); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/AccountViewModels.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/AccountViewModels.cs index c5c49181..030f88b5 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/AccountViewModels.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/AccountViewModels.cs @@ -1,112 +1,111 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -namespace OpenIddict.Sandbox.AspNet.Server.Models +namespace OpenIddict.Sandbox.AspNet.Server.Models; + +public class ExternalLoginConfirmationViewModel +{ + [Required] + [Display(Name = "Courrier électronique")] + public string Email { get; set; } +} + +public class ExternalLoginListViewModel +{ + public string ReturnUrl { get; set; } +} + +public class SendCodeViewModel +{ + public string SelectedProvider { get; set; } + public ICollection Providers { get; set; } + public string ReturnUrl { get; set; } + public bool RememberMe { get; set; } +} + +public class VerifyCodeViewModel +{ + [Required] + public string Provider { get; set; } + + [Required] + [Display(Name = "Code")] + public string Code { get; set; } + public string ReturnUrl { get; set; } + + [Display(Name = "Mémoriser ce navigateur ?")] + public bool RememberBrowser { get; set; } + + public bool RememberMe { get; set; } +} + +public class ForgotViewModel +{ + [Required] + [Display(Name = "Courrier électronique")] + public string Email { get; set; } +} + +public class LoginViewModel +{ + [Required] + [Display(Name = "Courrier électronique")] + [EmailAddress] + public string Email { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Mot de passe")] + public string Password { get; set; } + + [Display(Name = "Mémoriser mes informations")] + public bool RememberMe { get; set; } +} + +public class RegisterViewModel +{ + [Required] + [EmailAddress] + [Display(Name = "Courrier électronique")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "{0} doit contenir au moins {2} caractères.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Mot de passe")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirmer le mot de passe")] + [Compare("Password", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] + public string ConfirmPassword { get; set; } +} + +public class ResetPasswordViewModel +{ + [Required] + [EmailAddress] + [Display(Name = "Courrier électronique")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "{0} doit contenir au moins {2} caractères.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Mot de passe")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirmer le mot de passe")] + [Compare("Password", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] + public string ConfirmPassword { get; set; } + + public string Code { get; set; } +} + +public class ForgotPasswordViewModel { - public class ExternalLoginConfirmationViewModel - { - [Required] - [Display(Name = "Courrier électronique")] - public string Email { get; set; } - } - - public class ExternalLoginListViewModel - { - public string ReturnUrl { get; set; } - } - - public class SendCodeViewModel - { - public string SelectedProvider { get; set; } - public ICollection Providers { get; set; } - public string ReturnUrl { get; set; } - public bool RememberMe { get; set; } - } - - public class VerifyCodeViewModel - { - [Required] - public string Provider { get; set; } - - [Required] - [Display(Name = "Code")] - public string Code { get; set; } - public string ReturnUrl { get; set; } - - [Display(Name = "Mémoriser ce navigateur ?")] - public bool RememberBrowser { get; set; } - - public bool RememberMe { get; set; } - } - - public class ForgotViewModel - { - [Required] - [Display(Name = "Courrier électronique")] - public string Email { get; set; } - } - - public class LoginViewModel - { - [Required] - [Display(Name = "Courrier électronique")] - [EmailAddress] - public string Email { get; set; } - - [Required] - [DataType(DataType.Password)] - [Display(Name = "Mot de passe")] - public string Password { get; set; } - - [Display(Name = "Mémoriser mes informations")] - public bool RememberMe { get; set; } - } - - public class RegisterViewModel - { - [Required] - [EmailAddress] - [Display(Name = "Courrier électronique")] - public string Email { get; set; } - - [Required] - [StringLength(100, ErrorMessage = "{0} doit contenir au moins {2} caractères.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "Mot de passe")] - public string Password { get; set; } - - [DataType(DataType.Password)] - [Display(Name = "Confirmer le mot de passe")] - [Compare("Password", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] - public string ConfirmPassword { get; set; } - } - - public class ResetPasswordViewModel - { - [Required] - [EmailAddress] - [Display(Name = "Courrier électronique")] - public string Email { get; set; } - - [Required] - [StringLength(100, ErrorMessage = "{0} doit contenir au moins {2} caractères.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "Mot de passe")] - public string Password { get; set; } - - [DataType(DataType.Password)] - [Display(Name = "Confirmer le mot de passe")] - [Compare("Password", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] - public string ConfirmPassword { get; set; } - - public string Code { get; set; } - } - - public class ForgotPasswordViewModel - { - [Required] - [EmailAddress] - [Display(Name = "E-mail")] - public string Email { get; set; } - } + [Required] + [EmailAddress] + [Display(Name = "E-mail")] + public string Email { get; set; } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs index a27a7eca..b9847ebe 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs @@ -4,37 +4,36 @@ using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; -namespace OpenIddict.Sandbox.AspNet.Server.Models +namespace OpenIddict.Sandbox.AspNet.Server.Models; + +// Vous pouvez ajouter des données de profil pour l'utilisateur en ajoutant d'autres propriétés à votre classe ApplicationUser. Pour en savoir plus, consultez https://go.microsoft.com/fwlink/?LinkID=317594. +public class ApplicationUser : IdentityUser { - // Vous pouvez ajouter des données de profil pour l'utilisateur en ajoutant d'autres propriétés à votre classe ApplicationUser. Pour en savoir plus, consultez https://go.microsoft.com/fwlink/?LinkID=317594. - public class ApplicationUser : IdentityUser + public async Task GenerateUserIdentityAsync(UserManager manager) { - public async Task GenerateUserIdentityAsync(UserManager manager) - { - // Notez que l'authenticationType doit correspondre à celui défini dans CookieAuthenticationOptions.AuthenticationType - var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); - // Ajouter des revendications utilisateur personnalisées ici - return userIdentity; - } + // Notez que l'authenticationType doit correspondre à celui défini dans CookieAuthenticationOptions.AuthenticationType + var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); + // Ajouter des revendications utilisateur personnalisées ici + return userIdentity; } +} - public class ApplicationDbContext : IdentityDbContext +public class ApplicationDbContext : IdentityDbContext +{ + public ApplicationDbContext() + : base("DefaultConnection", throwIfV1Schema: false) { - public ApplicationDbContext() - : base("DefaultConnection", throwIfV1Schema: false) - { - } + } - public static ApplicationDbContext Create() - { - return new ApplicationDbContext(); - } + public static ApplicationDbContext Create() + { + return new ApplicationDbContext(); + } - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); - modelBuilder.UseOpenIddict(); - } + modelBuilder.UseOpenIddict(); } } \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/ManageViewModels.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/ManageViewModels.cs index 42ac8a50..3eecd67a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/ManageViewModels.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Models/ManageViewModels.cs @@ -3,84 +3,83 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNet.Identity; using Microsoft.Owin.Security; -namespace OpenIddict.Sandbox.AspNet.Server.Models +namespace OpenIddict.Sandbox.AspNet.Server.Models; + +public class IndexViewModel { - public class IndexViewModel - { - public bool HasPassword { get; set; } - public IList Logins { get; set; } - public string PhoneNumber { get; set; } - public bool TwoFactor { get; set; } - public bool BrowserRemembered { get; set; } - } + public bool HasPassword { get; set; } + public IList Logins { get; set; } + public string PhoneNumber { get; set; } + public bool TwoFactor { get; set; } + public bool BrowserRemembered { get; set; } +} - public class ManageLoginsViewModel - { - public IList CurrentLogins { get; set; } - public IList OtherLogins { get; set; } - } +public class ManageLoginsViewModel +{ + public IList CurrentLogins { get; set; } + public IList OtherLogins { get; set; } +} - public class FactorViewModel - { - public string Purpose { get; set; } - } +public class FactorViewModel +{ + public string Purpose { get; set; } +} - public class SetPasswordViewModel - { - [Required] - [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "Nouveau mot de passe")] - public string NewPassword { get; set; } +public class SetPasswordViewModel +{ + [Required] + [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Nouveau mot de passe")] + public string NewPassword { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirmer le nouveau mot de passe")] - [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] - public string ConfirmPassword { get; set; } - } + [DataType(DataType.Password)] + [Display(Name = "Confirmer le nouveau mot de passe")] + [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] + public string ConfirmPassword { get; set; } +} - public class ChangePasswordViewModel - { - [Required] - [DataType(DataType.Password)] - [Display(Name = "Mot de passe actuel")] - public string OldPassword { get; set; } +public class ChangePasswordViewModel +{ + [Required] + [DataType(DataType.Password)] + [Display(Name = "Mot de passe actuel")] + public string OldPassword { get; set; } - [Required] - [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "Nouveau mot de passe")] - public string NewPassword { get; set; } + [Required] + [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Nouveau mot de passe")] + public string NewPassword { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirmer le nouveau mot de passe")] - [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] - public string ConfirmPassword { get; set; } - } + [DataType(DataType.Password)] + [Display(Name = "Confirmer le nouveau mot de passe")] + [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] + public string ConfirmPassword { get; set; } +} - public class AddPhoneNumberViewModel - { - [Required] - [Phone] - [Display(Name = "Numéro de téléphone")] - public string Number { get; set; } - } +public class AddPhoneNumberViewModel +{ + [Required] + [Phone] + [Display(Name = "Numéro de téléphone")] + public string Number { get; set; } +} - public class VerifyPhoneNumberViewModel - { - [Required] - [Display(Name = "Code")] - public string Code { get; set; } +public class VerifyPhoneNumberViewModel +{ + [Required] + [Display(Name = "Code")] + public string Code { get; set; } - [Required] - [Phone] - [Display(Name = "Numéro de téléphone")] - public string PhoneNumber { get; set; } - } + [Required] + [Phone] + [Display(Name = "Numéro de téléphone")] + public string PhoneNumber { get; set; } +} - public class ConfigureTwoFactorViewModel - { - public string SelectedProvider { get; set; } - public ICollection Providers { get; set; } - } +public class ConfigureTwoFactorViewModel +{ + public string SelectedProvider { get; set; } + public ICollection Providers { get; set; } } \ No newline at end of file diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs index 3c57cb20..c0baedd4 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs @@ -22,257 +22,256 @@ using Owin; using static OpenIddict.Abstractions.OpenIddictConstants; [assembly: OwinStartup(typeof(OpenIddict.Sandbox.AspNet.Server.Startup))] -namespace OpenIddict.Sandbox.AspNet.Server +namespace OpenIddict.Sandbox.AspNet.Server; + +public class Startup { - public class Startup + public void Configuration(IAppBuilder app) { - public void Configuration(IAppBuilder app) - { - var services = new ServiceCollection(); + var services = new ServiceCollection(); - services.AddOpenIddict() + services.AddOpenIddict() - // Register the OpenIddict core components. - .AddCore(options => - { - // Configure OpenIddict to use the Entity Framework 6.x stores and models. - // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. - options.UseEntityFramework() - .UseDbContext(); - - // Developers who prefer using MongoDB can remove the previous lines - // and configure OpenIddict to use the specified MongoDB database: - // options.UseMongoDb() - // .UseDatabase(new MongoClient().GetDatabase("openiddict")); - }) - - // Register the OpenIddict client components. - .AddClient(options => - { - // Note: this sample uses the code flow, but you can enable the other flows if necessary. - options.AllowAuthorizationCodeFlow(); - - // Register the signing and encryption credentials used to protect - // sensitive data like the state tokens produced by OpenIddict. - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - // Register the OWIN host and configure the OWIN-specific options. - options.UseOwin() - .EnableRedirectionEndpointPassthrough() - .SetCookieManager(new SystemWebCookieManager()); - - // Register the System.Net.Http integration and use the identity of the current - // assembly as a more specific user agent, which can be useful when dealing with - // providers that use the user agent as a way to throttle requests (e.g Reddit). - options.UseSystemNetHttp() - .SetProductInformation(typeof(Startup).Assembly); - - // Register the Web providers integrations. - // - // Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint - // URI per provider, unless all the registered providers support returning a special "iss" - // parameter containing their URL as part of authorization responses. For more information, - // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4. - options.UseWebProviders() - .AddGitHub(options => - { - options.SetClientId("c4ade52327b01ddacff3") - .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") - .SetRedirectUri("callback/login/github"); - }); - }) - - // Register the OpenIddict server components. - .AddServer(options => - { - // Enable the authorization, device, introspection, - // logout, token, userinfo and verification endpoints. - options.SetAuthorizationEndpointUris("connect/authorize") - .SetDeviceEndpointUris("connect/device") - .SetIntrospectionEndpointUris("connect/introspect") - .SetLogoutEndpointUris("connect/logout") - .SetTokenEndpointUris("connect/token") - .SetUserinfoEndpointUris("connect/userinfo") - .SetVerificationEndpointUris("connect/verify"); - - // Note: this sample uses the code, device code, password and refresh token flows, but you - // can enable the other flows if you need to support implicit or client credentials. - options.AllowAuthorizationCodeFlow() - .AllowDeviceCodeFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow(); - - // Mark the "email", "profile", "roles" and "demo_api" scopes as supported scopes. - options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api"); - - // Register the signing and encryption credentials. - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - // Force client applications to use Proof Key for Code Exchange (PKCE). - options.RequireProofKeyForCodeExchange(); - - // Register the OWIN host and configure the OWIN-specific options. - options.UseOwin() - .EnableAuthorizationEndpointPassthrough() - .EnableLogoutEndpointPassthrough() - .EnableTokenEndpointPassthrough(); - }) - - // Register the OpenIddict validation components. - .AddValidation(options => - { - // Import the configuration from the local OpenIddict server instance. - options.UseLocalServer(); + // Register the OpenIddict core components. + .AddCore(options => + { + // Configure OpenIddict to use the Entity Framework 6.x stores and models. + // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. + options.UseEntityFramework() + .UseDbContext(); + + // Developers who prefer using MongoDB can remove the previous lines + // and configure OpenIddict to use the specified MongoDB database: + // options.UseMongoDb() + // .UseDatabase(new MongoClient().GetDatabase("openiddict")); + }) + + // Register the OpenIddict client components. + .AddClient(options => + { + // Note: this sample uses the code flow, but you can enable the other flows if necessary. + options.AllowAuthorizationCodeFlow(); + + // Register the signing and encryption credentials used to protect + // sensitive data like the state tokens produced by OpenIddict. + options.AddDevelopmentEncryptionCertificate() + .AddDevelopmentSigningCertificate(); + + // Register the OWIN host and configure the OWIN-specific options. + options.UseOwin() + .EnableRedirectionEndpointPassthrough() + .SetCookieManager(new SystemWebCookieManager()); + + // Register the System.Net.Http integration and use the identity of the current + // assembly as a more specific user agent, which can be useful when dealing with + // providers that use the user agent as a way to throttle requests (e.g Reddit). + options.UseSystemNetHttp() + .SetProductInformation(typeof(Startup).Assembly); + + // Register the Web providers integrations. + // + // Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint + // URI per provider, unless all the registered providers support returning a special "iss" + // parameter containing their URL as part of authorization responses. For more information, + // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4. + options.UseWebProviders() + .AddGitHub(options => + { + options.SetClientId("c4ade52327b01ddacff3") + .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") + .SetRedirectUri("callback/login/github"); + }); + }) + + // Register the OpenIddict server components. + .AddServer(options => + { + // Enable the authorization, device, introspection, + // logout, token, userinfo and verification endpoints. + options.SetAuthorizationEndpointUris("connect/authorize") + .SetDeviceEndpointUris("connect/device") + .SetIntrospectionEndpointUris("connect/introspect") + .SetLogoutEndpointUris("connect/logout") + .SetTokenEndpointUris("connect/token") + .SetUserinfoEndpointUris("connect/userinfo") + .SetVerificationEndpointUris("connect/verify"); + + // Note: this sample uses the code, device code, password and refresh token flows, but you + // can enable the other flows if you need to support implicit or client credentials. + options.AllowAuthorizationCodeFlow() + .AllowDeviceCodeFlow() + .AllowPasswordFlow() + .AllowRefreshTokenFlow(); + + // Mark the "email", "profile", "roles" and "demo_api" scopes as supported scopes. + options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api"); + + // Register the signing and encryption credentials. + options.AddDevelopmentEncryptionCertificate() + .AddDevelopmentSigningCertificate(); + + // Force client applications to use Proof Key for Code Exchange (PKCE). + options.RequireProofKeyForCodeExchange(); + + // Register the OWIN host and configure the OWIN-specific options. + options.UseOwin() + .EnableAuthorizationEndpointPassthrough() + .EnableLogoutEndpointPassthrough() + .EnableTokenEndpointPassthrough(); + }) + + // Register the OpenIddict validation components. + .AddValidation(options => + { + // Import the configuration from the local OpenIddict server instance. + options.UseLocalServer(); - // Register the OWIN host. - options.UseOwin(); - }); + // Register the OWIN host. + options.UseOwin(); + }); - // Create a new Autofac container and import the OpenIddict services. - var builder = new ContainerBuilder(); - builder.Populate(services); + // Create a new Autofac container and import the OpenIddict services. + var builder = new ContainerBuilder(); + builder.Populate(services); - // Register the MVC controllers. - builder.RegisterControllers(typeof(Startup).Assembly); + // Register the MVC controllers. + builder.RegisterControllers(typeof(Startup).Assembly); - // Register the Web API controllers. - builder.RegisterApiControllers(typeof(Startup).Assembly); + // Register the Web API controllers. + builder.RegisterApiControllers(typeof(Startup).Assembly); - var container = builder.Build(); + var container = builder.Build(); - // Register the Autofac scope injector middleware. - app.UseAutofacLifetimeScopeInjector(container); + // Register the Autofac scope injector middleware. + app.UseAutofacLifetimeScopeInjector(container); - // Register the Entity Framework context and the user/sign-in managers used by ASP.NET Identity. - app.CreatePerOwinContext(ApplicationDbContext.Create); - app.CreatePerOwinContext(ApplicationUserManager.Create); - app.CreatePerOwinContext(ApplicationSignInManager.Create); + // Register the Entity Framework context and the user/sign-in managers used by ASP.NET Identity. + app.CreatePerOwinContext(ApplicationDbContext.Create); + app.CreatePerOwinContext(ApplicationUserManager.Create); + app.CreatePerOwinContext(ApplicationSignInManager.Create); - // Register the cookie middleware used by ASP.NET Identity. - app.UseCookieAuthentication(new CookieAuthenticationOptions + // Register the cookie middleware used by ASP.NET Identity. + app.UseCookieAuthentication(new CookieAuthenticationOptions + { + AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, + LoginPath = new PathString("/Account/Login"), + Provider = new CookieAuthenticationProvider { - AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, - LoginPath = new PathString("/Account/Login"), - Provider = new CookieAuthenticationProvider - { - OnValidateIdentity = SecurityStampValidator.OnValidateIdentity( - validateInterval: TimeSpan.FromMinutes(30), - regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) - } - }); - - app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); - app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); - app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); + OnValidateIdentity = SecurityStampValidator.OnValidateIdentity( + validateInterval: TimeSpan.FromMinutes(30), + regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) + } + }); + + app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); + app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); + app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); + + // Register the OpenIddict middleware. + app.UseMiddlewareFromContainer(); + app.UseMiddlewareFromContainer(); + app.UseMiddlewareFromContainer(); + + // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances. + DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); + + // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances + // and infer the Web API routes using the HTTP attributes used in the controllers. + var configuration = new HttpConfiguration + { + DependencyResolver = new AutofacWebApiDependencyResolver(container) + }; - // Register the OpenIddict middleware. - app.UseMiddlewareFromContainer(); - app.UseMiddlewareFromContainer(); - app.UseMiddlewareFromContainer(); + configuration.MapHttpAttributeRoutes(); + configuration.SuppressDefaultHostAuthentication(); - // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances. - DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); + // Register the Autofac Web API integration and Web API middleware. + app.UseAutofacWebApi(configuration); + app.UseWebApi(configuration); - // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances - // and infer the Web API routes using the HTTP attributes used in the controllers. - var configuration = new HttpConfiguration - { - DependencyResolver = new AutofacWebApiDependencyResolver(container) - }; + // Seed the database with the sample client using the OpenIddict application manager. + // Note: in a real world application, this step should be part of a setup script. + Task.Run(async delegate + { + await using var scope = container.BeginLifetimeScope(); - configuration.MapHttpAttributeRoutes(); - configuration.SuppressDefaultHostAuthentication(); + var context = scope.Resolve(); + context.Database.CreateIfNotExists(); - // Register the Autofac Web API integration and Web API middleware. - app.UseAutofacWebApi(configuration); - app.UseWebApi(configuration); + var manager = scope.Resolve(); - // Seed the database with the sample client using the OpenIddict application manager. - // Note: in a real world application, this step should be part of a setup script. - Task.Run(async delegate + if (await manager.FindByClientIdAsync("mvc") is null) { - await using var scope = container.BeginLifetimeScope(); - - var context = scope.Resolve(); - context.Database.CreateIfNotExists(); - - var manager = scope.Resolve(); - - if (await manager.FindByClientIdAsync("mvc") is null) + await manager.CreateAsync(new OpenIddictApplicationDescriptor { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + ApplicationType = ApplicationTypes.Web, + ClientId = "mvc", + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", + ClientType = ClientTypes.Confidential, + ConsentType = ConsentTypes.Explicit, + DisplayName = "MVC client application", + RedirectUris = + { + new Uri("https://localhost:44378/callback/login/local") + }, + PostLogoutRedirectUris = + { + new Uri("https://localhost:44378/callback/logout/local") + }, + Permissions = { - ApplicationType = ApplicationTypes.Web, - ClientId = "mvc", - ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", - ClientType = ClientTypes.Confidential, - ConsentType = ConsentTypes.Explicit, - DisplayName = "MVC client application", - RedirectUris = - { - new Uri("https://localhost:44378/callback/login/local") - }, - PostLogoutRedirectUris = - { - new Uri("https://localhost:44378/callback/logout/local") - }, - Permissions = - { - Permissions.Endpoints.Authorization, - Permissions.Endpoints.Logout, - Permissions.Endpoints.Token, - Permissions.GrantTypes.AuthorizationCode, - Permissions.GrantTypes.RefreshToken, - Permissions.ResponseTypes.Code, - Permissions.Scopes.Email, - Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" - }, - Requirements = - { - Requirements.Features.ProofKeyForCodeExchange - } - }); - } - - if (await manager.FindByClientIdAsync("postman") is null) + Permissions.Endpoints.Authorization, + Permissions.Endpoints.Logout, + Permissions.Endpoints.Token, + Permissions.GrantTypes.AuthorizationCode, + Permissions.GrantTypes.RefreshToken, + Permissions.ResponseTypes.Code, + Permissions.Scopes.Email, + Permissions.Scopes.Profile, + Permissions.Scopes.Roles, + Permissions.Prefixes.Scope + "demo_api" + }, + Requirements = + { + Requirements.Features.ProofKeyForCodeExchange + } + }); + } + + if (await manager.FindByClientIdAsync("postman") is null) + { + await manager.CreateAsync(new OpenIddictApplicationDescriptor { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + ApplicationType = ApplicationTypes.Native, + ClientId = "postman", + ClientType = ClientTypes.Public, + ConsentType = ConsentTypes.Systematic, + DisplayName = "Postman", + RedirectUris = { - ApplicationType = ApplicationTypes.Native, - ClientId = "postman", - ClientType = ClientTypes.Public, - ConsentType = ConsentTypes.Systematic, - DisplayName = "Postman", - RedirectUris = - { - new Uri("https://oauth.pstmn.io/v1/callback") - }, - Permissions = - { - Permissions.Endpoints.Authorization, - Permissions.Endpoints.Device, - Permissions.Endpoints.Token, - Permissions.GrantTypes.AuthorizationCode, - Permissions.GrantTypes.DeviceCode, - Permissions.GrantTypes.Password, - Permissions.GrantTypes.RefreshToken, - Permissions.ResponseTypes.Code, - Permissions.Scopes.Email, - Permissions.Scopes.Profile, - Permissions.Scopes.Roles - }, - Settings = - { - // Use a shorter access token lifetime for tokens issued to the Postman application. - [Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture) - } - }); - } - }).GetAwaiter().GetResult(); - } + new Uri("https://oauth.pstmn.io/v1/callback") + }, + Permissions = + { + Permissions.Endpoints.Authorization, + Permissions.Endpoints.Device, + Permissions.Endpoints.Token, + Permissions.GrantTypes.AuthorizationCode, + Permissions.GrantTypes.DeviceCode, + Permissions.GrantTypes.Password, + Permissions.GrantTypes.RefreshToken, + Permissions.ResponseTypes.Code, + Permissions.Scopes.Email, + Permissions.Scopes.Profile, + Permissions.Scopes.Roles + }, + Settings = + { + // Use a shorter access token lifetime for tokens issued to the Postman application. + [Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture) + } + }); + } + }).GetAwaiter().GetResult(); } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/AuthorizeViewModel.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/AuthorizeViewModel.cs index 8beac773..d7c03180 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/AuthorizeViewModel.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/AuthorizeViewModel.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; using System.Web.Mvc; -namespace OpenIddict.Sandbox.AspNet.Server.ViewModels.Authorization +namespace OpenIddict.Sandbox.AspNet.Server.ViewModels.Authorization; + +[Bind(Exclude = nameof(Parameters))] +public class LogoutViewModel { - [Bind(Exclude = nameof(Parameters))] - public class LogoutViewModel - { - public IEnumerable> Parameters { get; internal set; } - } + public IEnumerable> Parameters { get; internal set; } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/LogoutViewModel.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/LogoutViewModel.cs index 601a7962..c1d47f1a 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/LogoutViewModel.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/LogoutViewModel.cs @@ -2,17 +2,16 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; -namespace OpenIddict.Sandbox.AspNet.Server.ViewModels.Authorization +namespace OpenIddict.Sandbox.AspNet.Server.ViewModels.Authorization; + +[Bind(Exclude = nameof(Parameters))] +public class AuthorizeViewModel { - [Bind(Exclude = nameof(Parameters))] - public class AuthorizeViewModel - { - [Display(Name = "Application")] - public string ApplicationName { get; set; } + [Display(Name = "Application")] + public string ApplicationName { get; set; } - [Display(Name = "Scope")] - public string Scope { get; set; } + [Display(Name = "Scope")] + public string Scope { get; set; } - public IEnumerable> Parameters { get; internal set; } - } + public IEnumerable> Parameters { get; internal set; } }