Browse Source

Update the ASP.NET 4.8 samples to use file-scoped namespaces

pull/2087/head
Kévin Chalet 2 years ago
parent
commit
63db215afc
  1. 541
      gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
  2. 37
      sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/BundleConfig.cs
  3. 11
      sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/FilterConfig.cs
  4. 23
      sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/RouteConfig.cs
  5. 353
      sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
  6. 145
      sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/HomeController.cs
  7. 17
      sandbox/OpenIddict.Sandbox.AspNet.Client/Global.asax.cs
  8. 27
      sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs
  9. 213
      sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
  10. 37
      sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/BundleConfig.cs
  11. 11
      sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/FilterConfig.cs
  12. 147
      sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs
  13. 23
      sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/RouteConfig.cs
  14. 735
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AccountController.cs
  15. 139
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs
  16. 681
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  17. 31
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/HomeController.cs
  18. 567
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ManageController.cs
  19. 83
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs
  20. 17
      sandbox/OpenIddict.Sandbox.AspNet.Server/Global.asax.cs
  21. 33
      sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/AsyncEnumerableExtensions.cs
  22. 47
      sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/FormValueRequiredAttribute.cs
  23. 213
      sandbox/OpenIddict.Sandbox.AspNet.Server/Models/AccountViewModels.cs
  24. 47
      sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs
  25. 135
      sandbox/OpenIddict.Sandbox.AspNet.Server/Models/ManageViewModels.cs
  26. 455
      sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
  27. 11
      sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/AuthorizeViewModel.cs
  28. 19
      sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/LogoutViewModel.cs

541
gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs

@ -5,47 +5,47 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Scriban; using Scriban;
namespace OpenIddict.Client.WebIntegration.Generators namespace OpenIddict.Client.WebIntegration.Generators;
[Generator]
public sealed class OpenIddictClientWebIntegrationGenerator : ISourceGenerator
{ {
[Generator] public void Execute(GeneratorExecutionContext context)
public sealed class OpenIddictClientWebIntegrationGenerator : ISourceGenerator
{ {
public void Execute(GeneratorExecutionContext context) var file = context.AdditionalFiles.Select(file => file.Path)
{ .Where(path => string.Equals(Path.GetFileName(path), "OpenIddictClientWebIntegrationProviders.xml"))
var file = context.AdditionalFiles.Select(file => file.Path) .SingleOrDefault();
.Where(path => string.Equals(Path.GetFileName(path), "OpenIddictClientWebIntegrationProviders.xml"))
.SingleOrDefault();
if (string.IsNullOrEmpty(file)) if (string.IsNullOrEmpty(file))
{ {
return; return;
} }
var document = XDocument.Load(file, LoadOptions.None); var document = XDocument.Load(file, LoadOptions.None);
context.AddSource( context.AddSource(
"OpenIddictClientWebIntegrationBuilder.generated.cs", "OpenIddictClientWebIntegrationBuilder.generated.cs",
SourceText.From(GenerateBuilderMethods(document), Encoding.UTF8)); SourceText.From(GenerateBuilderMethods(document), Encoding.UTF8));
context.AddSource( context.AddSource(
"OpenIddictClientWebIntegrationConfiguration.generated.cs", "OpenIddictClientWebIntegrationConfiguration.generated.cs",
SourceText.From(GenerateConfigurationClasses(document), Encoding.UTF8)); SourceText.From(GenerateConfigurationClasses(document), Encoding.UTF8));
context.AddSource( context.AddSource(
"OpenIddictClientWebIntegrationConstants.generated.cs", "OpenIddictClientWebIntegrationConstants.generated.cs",
SourceText.From(GenerateConstants(document), Encoding.UTF8)); SourceText.From(GenerateConstants(document), Encoding.UTF8));
context.AddSource( context.AddSource(
"OpenIddictClientWebIntegrationHelpers.generated.cs", "OpenIddictClientWebIntegrationHelpers.generated.cs",
SourceText.From(GenerateHelpers(document), Encoding.UTF8)); SourceText.From(GenerateHelpers(document), Encoding.UTF8));
context.AddSource( context.AddSource(
"OpenIddictClientWebIntegrationSettings.generated.cs", "OpenIddictClientWebIntegrationSettings.generated.cs",
SourceText.From(GenerateSettings(document), Encoding.UTF8)); SourceText.From(GenerateSettings(document), Encoding.UTF8));
static string GenerateBuilderMethods(XDocument document) static string GenerateBuilderMethods(XDocument document)
{ {
var template = Template.Parse(@"#nullable enable var template = Template.Parse(@"#nullable enable
#pragma warning disable CS0618 #pragma warning disable CS0618
using System.ComponentModel; using System.ComponentModel;
@ -743,62 +743,62 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder
{{~ end ~}} {{~ end ~}}
} }
"); ");
return template.Render(new return template.Render(new
{ {
Providers = document.Root.Elements("Provider") Providers = document.Root.Elements("Provider")
.Select(provider => new .Select(provider => new
{ {
Name = (string) provider.Attribute("Name"), Name = (string) provider.Attribute("Name"),
DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"),
Documentation = (string?) provider.Attribute("Documentation"), Documentation = (string?) provider.Attribute("Documentation"),
Obsolete = (bool?) provider.Attribute("Obsolete") ?? false, Obsolete = (bool?) provider.Attribute("Obsolete") ?? false,
Environments = provider.Elements("Environment").Select(environment => new Environments = provider.Elements("Environment").Select(environment => new
{ {
Name = (string?) environment.Attribute("Name") ?? "Production" Name = (string?) environment.Attribute("Name") ?? "Production"
}) })
.ToList(), .ToList(),
Settings = provider.Elements("Setting").Select(setting => new Settings = provider.Elements("Setting").Select(setting => new
{ {
PropertyName = (string) setting.Attribute("PropertyName"), PropertyName = (string) setting.Attribute("PropertyName"),
ParameterName = (string) setting.Attribute("ParameterName"), ParameterName = (string) setting.Attribute("ParameterName"),
Collection = (bool?) setting.Attribute("Collection") ?? false, Collection = (bool?) setting.Attribute("Collection") ?? false,
Obsolete = (bool?) setting.Attribute("Obsolete") ?? false, Obsolete = (bool?) setting.Attribute("Obsolete") ?? false,
Description = (string) setting.Attribute("Description") is string description ? Description = (string) setting.Attribute("Description") is string description ?
char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null, char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null,
ClrType = (string) setting.Attribute("Type") switch ClrType = (string) setting.Attribute("Type") switch
{ {
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value") "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
is "RS256" or "RS384" or "RS512" => "RsaSecurityKey", is "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey", is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "PS256" or "PS384" or "PS512" or is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey", "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"Certificate" => "X509Certificate2", "Certificate" => "X509Certificate2",
"String" => "string", "String" => "string",
"StringHashSet" => "HashSet<string>", "StringHashSet" => "HashSet<string>",
"Uri" => "Uri", "Uri" => "Uri",
string value => value string value => value
} }
})
.ToList()
}) })
.ToList() .ToList()
}); })
} .ToList()
});
}
static string GenerateConstants(XDocument document) static string GenerateConstants(XDocument document)
{ {
var template = Template.Parse(@"#nullable enable var template = Template.Parse(@"#nullable enable
namespace OpenIddict.Client.WebIntegration; namespace OpenIddict.Client.WebIntegration;
@ -838,34 +838,34 @@ public static partial class OpenIddictClientWebIntegrationConstants
} }
} }
"); ");
return template.Render(new return template.Render(new
{ {
Providers = document.Root.Elements("Provider") Providers = document.Root.Elements("Provider")
.Select(provider => new .Select(provider => new
{ {
Name = (string) provider.Attribute("Name"), Name = (string) provider.Attribute("Name"),
Id = (string) provider.Attribute("Id"), Id = (string) provider.Attribute("Id"),
Environments = provider.Elements("Environment").Select(environment => new Environments = provider.Elements("Environment").Select(environment => new
{ {
Name = (string?) environment.Attribute("Name") ?? "Production" Name = (string?) environment.Attribute("Name") ?? "Production"
}) })
.ToList(), .ToList(),
Properties = provider.Elements("Property").Select(property => new Properties = provider.Elements("Property").Select(property => new
{ {
Name = (string) property.Attribute("Name"), Name = (string) property.Attribute("Name"),
DictionaryKey = (string) property.Attribute("DictionaryKey") DictionaryKey = (string) property.Attribute("DictionaryKey")
})
.ToList(),
}) })
.ToList() .ToList(),
}); })
} .ToList()
});
}
static string GenerateConfigurationClasses(XDocument document) static string GenerateConfigurationClasses(XDocument document)
{ {
var template = Template.Parse(@"#nullable enable var template = Template.Parse(@"#nullable enable
#pragma warning disable CS0618 #pragma warning disable CS0618
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -1116,149 +1116,149 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
} }
} }
"); ");
return template.Render(new return template.Render(new
{ {
Providers = document.Root.Elements("Provider") Providers = document.Root.Elements("Provider")
.Select(provider => new .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"), Name = (string?) environment.Attribute("Name") ?? "Production",
DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"),
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 GrantTypesSupported = configuration.Elements("GrantType").ToList() switch
{
XElement configuration => new
{ {
AuthorizationEndpoint = (string?) configuration.Attribute("AuthorizationEndpoint"), { Count: > 0 } types => types.Select(type => (string?) type.Attribute("Value")).ToList(),
DeviceAuthorizationEndpoint = (string?) configuration.Attribute("DeviceAuthorizationEndpoint"),
IntrospectionEndpoint = (string?) configuration.Attribute("IntrospectionEndpoint"), // If no explicit grant type was set, assume the provider only supports the code flow.
RevocationEndpoint = (string?) configuration.Attribute("RevocationEndpoint"), _ => [GrantTypes.AuthorizationCode]
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]
}
}, },
_ => 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 _ => null
{ },
Name = (string) setting.Attribute("Name"),
Default = (bool?) setting.Attribute("Default") ?? false,
Required = (bool?) setting.Attribute("Required") ?? false
})
})
.ToList(),
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"), Settings = provider.Elements("Setting").Select(setting => new
Required = (bool?) setting.Attribute("Required") ?? false, {
Collection = (bool?) setting.Attribute("Collection") ?? false, PropertyName = (string) setting.Attribute("PropertyName"),
EncryptionAlgorithm = (string?) setting.Element("EncryptionAlgorithm")?.Attribute("Value"), Type = (string) setting.Attribute("Type"),
SigningAlgorithm = (string?) setting.Element("SigningAlgorithm")?.Attribute("Value"), 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 DefaultValue = (string?) setting.Attribute("DefaultValue"),
{
Value = (string) item.Attribute("Value"), Items = setting.Elements("Item").Select(item => new
Default = (bool?) item.Attribute("Default") ?? false, {
Required = (bool?) item.Attribute("Required") ?? false Value = (string) item.Attribute("Value"),
}) Default = (bool?) item.Attribute("Default") ?? false,
.ToList() Required = (bool?) item.Attribute("Required") ?? false
}) })
.ToList() .ToList()
}) })
.ToList() .ToList()
}); })
} .ToList()
});
}
static string GenerateHelpers(XDocument document) static string GenerateHelpers(XDocument document)
{ {
var template = Template.Parse(@"#nullable enable var template = Template.Parse(@"#nullable enable
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client; using OpenIddict.Client;
@ -1283,21 +1283,21 @@ public static partial class OpenIddictClientWebIntegrationHelpers
{{~ end ~}} {{~ end ~}}
} }
"); ");
return template.Render(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")
})
.ToList()
});
}
static string GenerateSettings(XDocument document)
{ {
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 System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@ -1335,52 +1335,51 @@ public sealed partial class OpenIddictClientWebIntegrationSettings
{{~ end ~}} {{~ end ~}}
} }
"); ");
return template.Render(new return template.Render(new
{ {
Providers = document.Root.Elements("Provider") Providers = document.Root.Elements("Provider")
.Select(provider => new .Select(provider => new
{ {
Name = (string) provider.Attribute("Name"), Name = (string) provider.Attribute("Name"),
DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"), DisplayName = (string?) provider.Attribute("DisplayName") ?? (string) provider.Attribute("Name"),
Settings = provider.Elements("Setting").Select(setting => new Settings = provider.Elements("Setting").Select(setting => new
{ {
PropertyName = (string) setting.Attribute("PropertyName"), PropertyName = (string) setting.Attribute("PropertyName"),
Collection = (bool?) setting.Attribute("Collection") ?? false, Collection = (bool?) setting.Attribute("Collection") ?? false,
Obsolete = (bool?) setting.Attribute("Obsolete") ?? false, Obsolete = (bool?) setting.Attribute("Obsolete") ?? false,
Description = (string) setting.Attribute("Description") is string description ? Description = (string) setting.Attribute("Description") is string description ?
char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null, char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description[1..] : null,
ClrType = (string) setting.Attribute("Type") switch ClrType = (string) setting.Attribute("Type") switch
{ {
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value") "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
is "RS256" or "RS384" or "RS512" => "RsaSecurityKey", is "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey", is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value") "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "PS256" or "PS384" or "PS512" or is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey", "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"Certificate" => "X509Certificate2", "Certificate" => "X509Certificate2",
"String" => "string", "String" => "string",
"StringHashSet" => "HashSet<string>", "StringHashSet" => "HashSet<string>",
"Uri" => "Uri", "Uri" => "Uri",
string value => value string value => value
} }
})
.ToList()
}) })
.ToList() .ToList()
}); })
} .ToList()
});
} }
}
public void Initialize(GeneratorInitializationContext context) public void Initialize(GeneratorInitializationContext context)
{ {
}
} }
} }

37
sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/BundleConfig.cs

@ -1,29 +1,28 @@
using System.Web.Optimization; 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 bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
public static void RegisterBundles(BundleCollection bundles) "~/Scripts/jquery-{version}.js"));
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*")); "~/Scripts/jquery.validate*"));
// Utilisez la version de développement de Modernizr pour développer et apprendre. Puis, lorsque vous êtes // 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. // 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( bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*")); "~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js")); "~/Scripts/bootstrap.js"));
bundles.Add(new StyleBundle("~/Content/css").Include( bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css", "~/Content/bootstrap.css",
"~/Content/site.css")); "~/Content/site.css"));
}
} }
} }

11
sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/FilterConfig.cs

@ -1,12 +1,11 @@
using System.Web.Mvc; 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());
}
} }
} }

23
sandbox/OpenIddict.Sandbox.AspNet.Client/App_Start/RouteConfig.cs

@ -1,21 +1,20 @@
using System.Web.Mvc; using System.Web.Mvc;
using System.Web.Routing; 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( routes.MapRoute(
name: "Default", name: "Default",
url: "{controller}/{action}/{id}", url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
); );
}
} }
} }

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

@ -11,212 +11,211 @@ using OpenIddict.Client;
using OpenIddict.Client.Owin; using OpenIddict.Client.Owin;
using static OpenIddict.Abstractions.OpenIddictConstants; 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) public AuthenticationController(OpenIddictClientService service)
=> _service = service; => _service = service;
[HttpPost, Route("~/login"), ValidateAntiForgeryToken] [HttpPost, Route("~/login"), ValidateAntiForgeryToken]
public async Task<ActionResult> LogIn(string provider, string returnUrl) public async Task<ActionResult> LogIn(string provider, string returnUrl)
{ {
var context = HttpContext.GetOwinContext(); var context = HttpContext.GetOwinContext();
// The local authorization server sample allows the client to select the external // 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, // 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 // 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). // the user is directly redirected to GitHub (in this case, no login page is shown).
if (string.Equals(provider, "Local+GitHub", StringComparison.Ordinal)) if (string.Equals(provider, "Local+GitHub", StringComparison.Ordinal))
{
var properties = new AuthenticationProperties(new Dictionary<string, string>
{ {
var properties = new AuthenticationProperties(new Dictionary<string, string> // Note: when only one client is registered in the client options,
{ // specifying the issuer URI or the provider name is not required.
// Note: when only one client is registered in the client options, [OpenIddictClientOwinConstants.Properties.ProviderName] = "Local",
// 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.
// Note: the OWIN host requires appending the #string suffix to indicate [Parameters.IdentityProvider + OpenIddictClientOwinConstants.PropertyTypes.String] = "GitHub"
// 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: OpenIddict always validates the specified provider name when handling the challenge operation, // Only allow local return URLs to prevent open redirect attacks.
// but the provider can also be validated earlier to return an error page or a special HTTP error code. RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"
var registrations = await _service.GetClientRegistrationsAsync(); };
if (!registrations.Any(registration => string.Equals(registration.ProviderName, provider, StringComparison.Ordinal)))
{ // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
return new HttpStatusCodeResult(400); context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType);
} return new EmptyResult();
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
// 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();
}
} }
[HttpPost, Route("~/logout"), ValidateAntiForgeryToken] else
public async Task<ActionResult> LogOut(string returnUrl)
{ {
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, var properties = new AuthenticationProperties(new Dictionary<string, string>
// this indicate that the user is already logged out locally (or has not logged in yet). {
var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); // Note: when only one client is registered in the client options,
if (result is not { Identity: ClaimsIdentity identity }) // 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. // 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. // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
context.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType); context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType);
return new EmptyResult();
}
}
// Extract the client registration identifier and retrieve the associated server configuration. [HttpPost, Route("~/logout"), ValidateAntiForgeryToken]
// If the provider is known to support remote sign-out, ask OpenIddict to initiate a logout request. public async Task<ActionResult> LogOut(string returnUrl)
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<string, string>
{
[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();
}
// 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. // Only allow local return URLs to prevent open redirect attacks.
return Redirect(Url.IsLocalUrl(returnUrl) ? returnUrl : "/"); return Redirect(Url.IsLocalUrl(returnUrl) ? returnUrl : "/");
} }
// Note: this controller uses the same callback action for all providers // Remove the local authentication cookie before triggering a redirection to the remote server.
// but for users who prefer using a different action per provider, context.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
// the following action can be split into separate actions.
[AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")] // Extract the client registration identifier and retrieve the associated server configuration.
public async Task<ActionResult> LogInCallback() // 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<string, string>
{
[OpenIddictClientOwinConstants.Properties.RegistrationId] = identifier,
// Retrieve the authorization data validated by OpenIddict as part of the callback handling. // While not required, the specification encourages sending an id_token_hint
var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); // 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: // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
// context.Authentication.SignOut(properties, OpenIddictClientOwinDefaults.AuthenticationType);
// * Directly using the tokens to perform the necessary action(s) on behalf of the user, which is suitable return new EmptyResult();
// 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 // Only allow local return URLs to prevent open redirect attacks.
// action(s) on their behalf by making API calls using the access token returned by the remote server. return Redirect(Url.IsLocalUrl(returnUrl) ? returnUrl : "/");
// }
// * 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 // Note: this controller uses the same callback action for all providers
// Core Identity, the UserManager.SetAuthenticationTokenAsync() API can be used to store external tokens. // but for users who prefer using a different action per provider,
// // the following action can be split into separate actions.
// Note: in this case, it's recommended to use column encryption to protect the tokens in the database. [AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")]
public async Task<ActionResult> 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 // Preserve the registration details to be able to resolve them later.
// 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 or Claims.Private.RegistrationId or Claims.Private.ProviderName
// 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 // The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim.
// 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 or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider");
// 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 var identity = new ClaimsIdentity(claims,
// authenticationType: CookieAuthenticationDefaults.AuthenticationType,
// Preserve the registration details to be able to resolve them later. nameType: ClaimTypes.Name,
// roleType: ClaimTypes.Role);
or Claims.Private.RegistrationId or Claims.Private.ProviderName
// // Build the authentication properties based on the properties that were added when the challenge was triggered.
// The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim. var properties = new AuthenticationProperties(result.Properties.Dictionary
// .Where(item => item.Key is
or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"); // Preserve the return URL.
".redirect" or
var identity = new ClaimsIdentity(claims,
authenticationType: CookieAuthenticationDefaults.AuthenticationType, // If needed, the tokens returned by the authorization server can be stored in the authentication cookie.
nameType: ClaimTypes.Name, OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or
roleType: ClaimTypes.Role); OpenIddictClientOwinConstants.Tokens.BackchannelIdentityToken or
OpenIddictClientOwinConstants.Tokens.RefreshToken)
// Build the authentication properties based on the properties that were added when the challenge was triggered. .ToDictionary(pair => pair.Key, pair => pair.Value));
var properties = new AuthenticationProperties(result.Properties.Dictionary
.Where(item => item.Key is context.Authentication.SignIn(properties, identity);
// Preserve the return URL. return Redirect(properties.RedirectUri ?? "/");
".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 // Note: this controller uses the same callback action for all providers
// but for users who prefer using a different action per provider, // but for users who prefer using a different action per provider,
// the following action can be split into separate actions. // the following action can be split into separate actions.
[AcceptVerbs("GET", "POST"), Route("~/callback/logout/{provider}")] [AcceptVerbs("GET", "POST"), Route("~/callback/logout/{provider}")]
public async Task<ActionResult> LogOutCallback() public async Task<ActionResult> LogOutCallback()
{ {
var context = HttpContext.GetOwinContext(); var context = HttpContext.GetOwinContext();
// Retrieve the data stored by OpenIddict in the state token created when the logout was triggered. // Retrieve the data stored by OpenIddict in the state token created when the logout was triggered.
var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType);
// In this sample, the local authentication cookie is always removed before the user agent is redirected // 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 // 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. // 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 ?? "/");
}
} }
} }

145
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.Abstractions.OpenIddictConstants;
using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; 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; _httpClientFactory = httpClientFactory;
private readonly OpenIddictClientService _service; _service = service;
}
public HomeController( [HttpGet, Route("~/")]
IHttpClientFactory httpClientFactory, public async Task<ActionResult> Index(CancellationToken cancellationToken) => View(new IndexViewModel
OpenIddictClientService service) {
{ Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken)
_httpClientFactory = httpClientFactory; where !string.IsNullOrEmpty(registration.ProviderName)
_service = service; where !string.IsNullOrEmpty(registration.ProviderDisplayName)
} select registration
});
[Authorize, HttpPost, Route("~/message"), ValidateAntiForgeryToken]
public async Task<ActionResult> GetMessage(CancellationToken cancellationToken)
{
var context = HttpContext.GetOwinContext();
[HttpGet, Route("~/")] var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType);
public async Task<ActionResult> Index(CancellationToken cancellationToken) => View(new IndexViewModel 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) Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken)
where !string.IsNullOrEmpty(registration.ProviderName) where !string.IsNullOrEmpty(registration.ProviderName)
where !string.IsNullOrEmpty(registration.ProviderDisplayName) where !string.IsNullOrEmpty(registration.ProviderDisplayName)
select registration select registration
}); });
}
[Authorize, HttpPost, Route("~/message"), ValidateAntiForgeryToken] [Authorize, HttpPost, Route("~/refresh-token")]
public async Task<ActionResult> GetMessage(CancellationToken cancellationToken) [ValidateAntiForgeryToken]
{ public async Task<ActionResult> RefreshToken(CancellationToken cancellationToken)
var context = HttpContext.GetOwinContext(); {
var context = HttpContext.GetOwinContext();
var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); var ticket = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType);
var token = result.Properties.Dictionary[Tokens.BackchannelAccessToken]; 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"); var properties = new AuthenticationProperties(ticket.Properties.Dictionary)
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); {
RedirectUri = null
};
using var response = await client.SendAsync(request, cancellationToken); properties.Dictionary[Tokens.BackchannelAccessToken] = result.AccessToken;
response.EnsureSuccessStatusCode();
return View("Index", new IndexViewModel if (!string.IsNullOrEmpty(result.RefreshToken))
{ {
Message = await response.Content.ReadAsStringAsync(), properties.Dictionary[Tokens.RefreshToken] = result.RefreshToken;
Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken)
where !string.IsNullOrEmpty(registration.ProviderName)
where !string.IsNullOrEmpty(registration.ProviderDisplayName)
select registration
});
} }
[Authorize, HttpPost, Route("~/refresh-token")] context.Authentication.SignIn(properties, ticket.Identity);
[ValidateAntiForgeryToken]
public async Task<ActionResult> RefreshToken(CancellationToken cancellationToken) return View("Index", new IndexViewModel
{ {
var context = HttpContext.GetOwinContext(); Message = result.AccessToken,
Providers = from registration in await _service.GetClientRegistrationsAsync(cancellationToken)
var ticket = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType); where !string.IsNullOrEmpty(registration.ProviderName)
if (!ticket.Properties.Dictionary.TryGetValue(Tokens.RefreshToken, out string token)) where !string.IsNullOrEmpty(registration.ProviderDisplayName)
{ select registration
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
});
}
} }
} }

17
sandbox/OpenIddict.Sandbox.AspNet.Client/Global.asax.cs

@ -2,16 +2,15 @@
using System.Web.Optimization; using System.Web.Optimization;
using System.Web.Routing; 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);
AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); BundleConfig.RegisterBundles(BundleTable.Bundles);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
} }
} }

27
sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs

@ -1,23 +1,22 @@
using System.Data.Entity; 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) protected override void OnModelCreating(DbModelBuilder modelBuilder)
{ {
modelBuilder.UseOpenIddict(); modelBuilder.UseOpenIddict();
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
// Customize the ASP.NET Identity model and override the defaults if needed. // 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. // For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder); // Add your customizations after calling base.OnModelCreating(builder);
}
} }
} }

213
sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs

@ -13,127 +13,126 @@ using OpenIddict.Sandbox.AspNetCore.Server.Models;
using Owin; using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants; 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. // Register the OpenIddict core components.
.AddCore(options => .AddCore(options =>
{ {
// Configure OpenIddict to use the Entity Framework 6.x stores and models. // Configure OpenIddict to use the Entity Framework 6.x stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFramework() options.UseEntityFramework()
.UseDbContext<ApplicationDbContext>(); .UseDbContext<ApplicationDbContext>();
// Developers who prefer using MongoDB can remove the previous lines // Developers who prefer using MongoDB can remove the previous lines
// and configure OpenIddict to use the specified MongoDB database: // and configure OpenIddict to use the specified MongoDB database:
// options.UseMongoDb() // options.UseMongoDb()
// .UseDatabase(new MongoClient().GetDatabase("openiddict")); // .UseDatabase(new MongoClient().GetDatabase("openiddict"));
}) })
// Register the OpenIddict client components. // Register the OpenIddict client components.
.AddClient(options => .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 Issuer = new Uri("https://localhost:44349/", UriKind.Absolute),
// flows, but you can enable the other flows if necessary. ProviderName = "Local",
options.AllowAuthorizationCodeFlow() ProviderDisplayName = "Local OIDC server",
.AllowRefreshTokenFlow();
ClientId = "mvc",
// Register the signing and encryption credentials used to protect ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
// sensitive data like the state tokens produced by OpenIddict. Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" },
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate(); RedirectUri = new Uri("callback/login/local", UriKind.Relative),
PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative)
// 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);
});
}); });
// Create a new Autofac container and import the OpenIddict services. // Register the Web providers integrations.
var builder = new ContainerBuilder(); //
builder.Populate(services); // 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. // Create a new Autofac container and import the OpenIddict services.
builder.RegisterControllers(typeof(Startup).Assembly); 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. var container = builder.Build();
app.UseAutofacLifetimeScopeInjector(container);
// Register the cookie middleware responsible for storing the user sessions. // Register the Autofac scope injector middleware.
app.UseCookieAuthentication(new CookieAuthenticationOptions app.UseAutofacLifetimeScopeInjector(container);
{
ExpireTimeSpan = TimeSpan.FromMinutes(50),
SlidingExpiration = false
});
// Register the OpenIddict middleware. // Register the cookie middleware responsible for storing the user sessions.
app.UseMiddlewareFromContainer<OpenIddictClientOwinMiddleware>(); app.UseCookieAuthentication(new CookieAuthenticationOptions
{
ExpireTimeSpan = TimeSpan.FromMinutes(50),
SlidingExpiration = false
});
// Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances. // Register the OpenIddict middleware.
DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); app.UseMiddlewareFromContainer<OpenIddictClientOwinMiddleware>();
// Create the database used by the OpenIddict client stack to store tokens. // Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances.
// Note: in a real world application, this step should be part of a setup script. DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
Task.Run(async delegate
{ // Create the database used by the OpenIddict client stack to store tokens.
await using var scope = container.BeginLifetimeScope(); // 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<ApplicationDbContext>(); var context = scope.Resolve<ApplicationDbContext>();
context.Database.CreateIfNotExists(); context.Database.CreateIfNotExists();
}).GetAwaiter().GetResult(); }).GetAwaiter().GetResult();
}
} }
} }

37
sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/BundleConfig.cs

@ -1,29 +1,28 @@
using System.Web.Optimization; 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 bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
public static void RegisterBundles(BundleCollection bundles) "~/Scripts/jquery-{version}.js"));
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*")); "~/Scripts/jquery.validate*"));
// Utilisez la version de développement de Modernizr pour développer et apprendre. Puis, lorsque vous êtes // 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. // 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( bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*")); "~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js")); "~/Scripts/bootstrap.js"));
bundles.Add(new StyleBundle("~/Content/css").Include( bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css", "~/Content/bootstrap.css",
"~/Content/site.css")); "~/Content/site.css"));
}
} }
} }

11
sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/FilterConfig.cs

@ -1,12 +1,11 @@
using System.Web.Mvc; 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());
}
} }
} }

147
sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs

@ -8,98 +8,97 @@ using Microsoft.Owin;
using Microsoft.Owin.Security; using Microsoft.Owin.Security;
using OpenIddict.Sandbox.AspNet.Server.Models; 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. // 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<ApplicationUser> public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{ {
public ApplicationUserManager(IUserStore<ApplicationUser> store) }
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configurer la logique de validation pour les noms d'utilisateur
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{ {
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>())); AllowOnlyAlphanumericUserNames = false,
// Configurer la logique de validation pour les noms d'utilisateur RequireUniqueEmail = true
manager.UserValidator = new UserValidator<ApplicationUser>(manager) };
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configurer la logique de validation pour les mots de passe // Configurer la logique de validation pour les mots de passe
manager.PasswordValidator = new PasswordValidator manager.PasswordValidator = new PasswordValidator
{ {
RequiredLength = 6, RequiredLength = 6,
RequireNonLetterOrDigit = true, RequireNonLetterOrDigit = true,
RequireDigit = true, RequireDigit = true,
RequireLowercase = true, RequireLowercase = true,
RequireUppercase = true, RequireUppercase = true,
}; };
// Configurer les valeurs par défaut du verrouillage de l'utilisateur // Configurer les valeurs par défaut du verrouillage de l'utilisateur
manager.UserLockoutEnabledByDefault = true; manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 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 // 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. // Vous pouvez écrire votre propre fournisseur et le connecter ici.
manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider<ApplicationUser> manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider<ApplicationUser>
{ {
MessageFormat = "Votre code de sécurité est {0}" MessageFormat = "Votre code de sécurité est {0}"
}); });
manager.RegisterTwoFactorProvider("Code d'e-mail", new EmailTokenProvider<ApplicationUser> manager.RegisterTwoFactorProvider("Code d'e-mail", new EmailTokenProvider<ApplicationUser>
{ {
Subject = "Code de sécurité", Subject = "Code de sécurité",
BodyFormat = "Votre code de sécurité est {0}" BodyFormat = "Votre code de sécurité est {0}"
}); });
manager.EmailService = new EmailService(); manager.EmailService = new EmailService();
manager.SmsService = new SmsService(); manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider; var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null) if (dataProtectionProvider != null)
{ {
manager.UserTokenProvider = manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity")); new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
} }
return manager;
} }
}
// Configurer le gestionnaire de connexion d'application qui est utilisé dans cette application. // Configurer le gestionnaire de connexion d'application qui est utilisé dans cette application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string> public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{ {
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) }
: base(userManager, authenticationManager)
{
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user) public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{ {
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
} }
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context) public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{ {
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication); return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
} }
} }

23
sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/RouteConfig.cs

@ -1,21 +1,20 @@
using System.Web.Mvc; using System.Web.Mvc;
using System.Web.Routing; 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( routes.MapRoute(
name: "Default", name: "Default",
url: "{controller}/{action}/{id}", url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
); );
}
} }
} }

735
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AccountController.cs

@ -7,476 +7,475 @@ using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security; using Microsoft.Owin.Security;
using OpenIddict.Sandbox.AspNet.Server.Models; using OpenIddict.Sandbox.AspNet.Server.Models;
namespace OpenIddict.Sandbox.AspNet.Server.Controllers namespace OpenIddict.Sandbox.AspNet.Server.Controllers;
[Authorize]
public class AccountController : Controller
{ {
[Authorize] private ApplicationSignInManager _signInManager;
public class AccountController : Controller 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; return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
SignInManager = signInManager; }
private set
{
_signInManager = value;
} }
}
public ApplicationSignInManager SignInManager public ApplicationUserManager UserManager
{
get
{ {
get return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
}
} }
private set
public ApplicationUserManager UserManager
{ {
get _userManager = value;
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
} }
}
//
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
// //
// GET: /Account/Login // POST: /Account/Login
[AllowAnonymous] [HttpPost]
public ActionResult Login(string returnUrl) [AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{ {
ViewBag.ReturnUrl = returnUrl; return View(model);
return View();
} }
// // Ceci ne comptabilise pas les échecs de connexion pour le verrouillage du compte
// POST: /Account/Login // Pour que les échecs de mot de passe déclenchent le verrouillage du compte, utilisez shouldLockout: true
[HttpPost] var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
[AllowAnonymous] switch (result)
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{ {
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); 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 // GET: /Account/VerifyCode
[AllowAnonymous] [AllowAnonymous]
public async Task<ActionResult> VerifyCode(string provider, string returnUrl, bool rememberMe) public async Task<ActionResult> 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 return View("Error");
if (!await SignInManager.HasBeenVerifiedAsync())
{
return View("Error");
}
return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe });
} }
return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe });
}
// //
// POST: /Account/VerifyCode // POST: /Account/VerifyCode
[HttpPost] [HttpPost]
[AllowAnonymous] [AllowAnonymous]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model) public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
if (!ModelState.IsValid)
{ {
if (!ModelState.IsValid) return View(model);
{
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);
}
} }
// // Le code suivant protège des attaques par force brute contre les codes à 2 facteurs.
// GET: /Account/Register // Si un utilisateur entre des codes incorrects pendant un certain intervalle, le compte de cet utilisateur
[AllowAnonymous] // est alors verrouillé pendant une durée spécifiée.
public ActionResult Register() // 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 // POST: /Account/Register
[HttpPost] [HttpPost]
[AllowAnonymous] [AllowAnonymous]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model) public async Task<ActionResult> 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 }; await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded) // 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
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false); // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// 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 // await UserManager.SendEmailAsync(user.Id, "Confirmer votre compte", "Confirmez votre compte en cliquant <a href=\"" + callbackUrl + "\">ici</a>");
// Envoyer un e-mail avec ce lien
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); return RedirectToAction("Index", "Home");
// 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 <a href=\"" + callbackUrl + "\">ici</a>");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
} }
AddErrors(result);
}
// Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire
return View(model); return View(model);
}
//
// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> 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 // POST: /Account/ForgotPassword
[AllowAnonymous] [HttpPost]
public async Task<ActionResult> ConfirmEmail(string userId, string code) [AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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");
}
// // 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
// GET: /Account/ForgotPassword // Envoyer un e-mail avec ce lien
[AllowAnonymous] // string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
public ActionResult ForgotPassword() // 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 <a href=\"" + callbackUrl + "\">ici</a>");
return View(); // return RedirectToAction("ForgotPasswordConfirmation", "Account");
} }
// // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire
// POST: /Account/ForgotPassword return View(model);
[HttpPost] }
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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");
}
// 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 // GET: /Account/ForgotPasswordConfirmation
// string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id); [AllowAnonymous]
// var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); public ActionResult ForgotPasswordConfirmation()
// await UserManager.SendEmailAsync(user.Id, "Réinitialiser le mot de passe", "Réinitialisez votre mot de passe en cliquant <a href=\"" + callbackUrl + "\">ici</a>"); {
// return RedirectToAction("ForgotPasswordConfirmation", "Account"); 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<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model); return View(model);
} }
var user = await UserManager.FindByNameAsync(model.Email);
// if (user == null)
// GET: /Account/ForgotPasswordConfirmation
[AllowAnonymous]
public ActionResult ForgotPasswordConfirmation()
{ {
return View(); // 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)
// GET: /Account/ResetPassword
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{ {
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 // GET: /Account/SendCode
[HttpPost] [AllowAnonymous]
[AllowAnonymous] public async Task<ActionResult> SendCode(string returnUrl, bool rememberMe)
[ValidateAntiForgeryToken] {
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model) var userId = await SignInManager.GetVerifiedUserIdAsync();
if (userId == null)
{ {
if (!ModelState.IsValid) return View("Error");
{
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();
} }
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 // POST: /Account/SendCode
[AllowAnonymous] [HttpPost]
public ActionResult ResetPasswordConfirmation() [AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
if (!ModelState.IsValid)
{ {
return View(); return View();
} }
// // Générer le jeton et l'envoyer
// POST: /Account/ExternalLogin if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{ {
// Demander une redirection vers le fournisseur de connexion externe return View("Error");
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
} }
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
}
// //
// GET: /Account/SendCode // GET: /Account/ExternalLoginCallback
[AllowAnonymous] [AllowAnonymous]
public async Task<ActionResult> SendCode(string returnUrl, bool rememberMe) public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{ {
var userId = await SignInManager.GetVerifiedUserIdAsync(); return RedirectToAction("Login");
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 });
} }
// // Connecter cet utilisateur à ce fournisseur de connexion externe si l'utilisateur possède déjà une connexion
// POST: /Account/SendCode var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
[HttpPost] switch (result)
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{ {
if (!ModelState.IsValid) case SignInStatus.Success:
{ return RedirectToLocal(returnUrl);
return View(); case SignInStatus.LockedOut:
} return View("Lockout");
case SignInStatus.RequiresVerification:
// Générer le jeton et l'envoyer return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider)) case SignInStatus.Failure:
{ default:
return View("Error"); // Si l'utilisateur n'a pas de compte, invitez alors celui-ci à créer un compte
} ViewBag.ReturnUrl = returnUrl;
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
} }
}
// //
// GET: /Account/ExternalLoginCallback // POST: /Account/ExternalLoginConfirmation
[AllowAnonymous] [HttpPost]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl) [AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{ {
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); return RedirectToAction("Index", "Manage");
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 });
}
} }
// if (ModelState.IsValid)
// POST: /Account/ExternalLoginConfirmation
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{ {
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");
} }
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
if (ModelState.IsValid) var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{ {
// Obtenir des informations sur l'utilisateur à partir du fournisseur de connexion externe result = await UserManager.AddLoginAsync(user.Id, info.Login);
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);
if (result.Succeeded) if (result.Succeeded)
{ {
result = await UserManager.AddLoginAsync(user.Id, info.Login); await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
if (result.Succeeded) return RedirectToLocal(returnUrl);
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
} }
AddErrors(result);
} }
AddErrors(result);
ViewBag.ReturnUrl = returnUrl;
return View(model);
} }
// ViewBag.ReturnUrl = returnUrl;
// POST: /Account/LogOff return View(model);
[HttpPost] }
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Index", "Home");
}
// //
// GET: /Account/ExternalLoginFailure // POST: /Account/LogOff
[AllowAnonymous] [HttpPost]
public ActionResult ExternalLoginFailure() [ValidateAntiForgeryToken]
{ public ActionResult LogOff()
return View(); {
} 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;
_userManager.Dispose();
_userManager = null;
}
if (_signInManager != null)
{
_signInManager.Dispose();
_signInManager = null;
}
} }
base.Dispose(disposing); if (_signInManager != null)
{
_signInManager.Dispose();
_signInManager = null;
}
} }
#region Applications auxiliaires base.Dispose(disposing);
// Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes }
private const string XsrfKey = "XsrfId";
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 Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
} }
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) public ChallengeResult(string provider, string redirectUri, string userId)
{ {
LoginProvider = provider; LoginProvider = provider;
RedirectUri = redirectUri; RedirectUri = redirectUri;
UserId = userId; UserId = userId;
} }
public string LoginProvider { get; set; } public string LoginProvider { get; set; }
public string RedirectUri { get; set; } public string RedirectUri { get; set; }
public string UserId { 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 }; properties.Dictionary[XsrfKey] = UserId;
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
} }
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
} }
#endregion
} }
#endregion
} }

139
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs

@ -9,87 +9,86 @@ using Microsoft.Owin.Security;
using OpenIddict.Client.Owin; using OpenIddict.Client.Owin;
using static OpenIddict.Abstractions.OpenIddictConstants; 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<ActionResult> LogInCallback()
{ {
// Note: this controller uses the same callback action for all providers var context = HttpContext.GetOwinContext();
// 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<ActionResult> LogInCallback()
{
var context = HttpContext.GetOwinContext();
// Retrieve the authorization data validated by OpenIddict as part of the callback handling. // Retrieve the authorization data validated by OpenIddict as part of the callback handling.
var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType); 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: // 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 // * 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 // 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). // 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 // 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. // 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 // * 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 // 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. // 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. // 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 // Preserve the registration details to be able to resolve them later.
// 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 or Claims.Private.RegistrationId or Claims.Private.ProviderName
// 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 // The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim.
// 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 or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider");
// 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");
// Note: when using external authentication providers with ASP.NET Identity, // Note: when using external authentication providers with ASP.NET Identity,
// the user identity MUST be added to the external authentication cookie scheme. // the user identity MUST be added to the external authentication cookie scheme.
var identity = new ClaimsIdentity(claims, var identity = new ClaimsIdentity(claims,
authenticationType: DefaultAuthenticationTypes.ExternalCookie, authenticationType: DefaultAuthenticationTypes.ExternalCookie,
nameType: ClaimTypes.Name, nameType: ClaimTypes.Name,
roleType: ClaimTypes.Role); roleType: ClaimTypes.Role);
// Build the authentication properties based on the properties that were added when the challenge was triggered. // Build the authentication properties based on the properties that were added when the challenge was triggered.
var properties = new AuthenticationProperties(result.Properties.Dictionary var properties = new AuthenticationProperties(result.Properties.Dictionary
.Where(item => item.Key is .Where(item => item.Key is
// Preserve the return URL. // Preserve the return URL.
".redirect" or ".redirect" or
// If needed, the tokens returned by the authorization server can be stored in the authentication cookie. // If needed, the tokens returned by the authorization server can be stored in the authentication cookie.
OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or
OpenIddictClientOwinConstants.Tokens.RefreshToken) OpenIddictClientOwinConstants.Tokens.RefreshToken)
.ToDictionary(pair => pair.Key, pair => pair.Value)); .ToDictionary(pair => pair.Key, pair => pair.Value));
context.Authentication.SignIn(properties, identity); context.Authentication.SignIn(properties, identity);
return Redirect(properties.RedirectUri ?? "/"); return Redirect(properties.RedirectUri ?? "/");
}
} }
} }

681
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

@ -24,229 +24,106 @@ using OpenIddict.Server.Owin;
using Owin; using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants; 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; _applicationManager = applicationManager;
private readonly IOpenIddictAuthorizationManager _authorizationManager; _authorizationManager = authorizationManager;
private readonly OpenIddictClientService _clientService; _clientService = clientService;
private readonly IOpenIddictScopeManager _scopeManager; _scopeManager = scopeManager;
}
public AuthorizationController(
IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager,
OpenIddictClientService clientService,
IOpenIddictScopeManager scopeManager)
{
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_clientService = clientService;
_scopeManager = scopeManager;
}
[HttpGet, Route("~/connect/authorize")] [HttpGet, Route("~/connect/authorize")]
public async Task<ActionResult> Authorize() public async Task<ActionResult> 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(); // For applications that want to allow the client to select the external authentication provider
var request = context.GetOpenIddictServerRequest() ?? // that will be used to authenticate the user, the identity_provider parameter can be used for that.
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); if (!string.IsNullOrEmpty(request.IdentityProvider))
// 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 var registrations = await _clientService.GetClientRegistrationsAsync();
// that will be used to authenticate the user, the identity_provider parameter can be used for that. if (!registrations.Any(registration => string.Equals(registration.ProviderName,
if (!string.IsNullOrEmpty(request.IdentityProvider)) 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<string, string>
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidRequest,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] =
"The specified identity provider is not valid."
}));
return new EmptyResult();
}
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
// 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<ApplicationUserManager>().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( context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string> properties: new AuthenticationProperties(new Dictionary<string, string>
{ {
[OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, [OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidRequest,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = [OpenIddictServerOwinConstants.Properties.ErrorDescription] =
"The logged in user is not allowed to access this client application." "The specified identity provider is not valid."
}));
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<ApplicationUserManager>().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<string, string>
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] =
"Interactive user consent is required."
})); }));
return new EmptyResult(); return new EmptyResult();
}
// In every other case, render the consent form. var properties = new AuthenticationProperties(new Dictionary<string, string>
default: return View(new AuthorizeViewModel
{ {
ApplicationName = await _applicationManager.GetDisplayNameAsync(application), // Note: when only one client is registered in the client options,
Scope = request.Scope, // specifying the issuer URI or the provider name is not required.
[OpenIddictClientOwinConstants.Properties.ProviderName] = request.IdentityProvider
// 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 // Once the callback is handled, redirect the user agent to the ASP.NET Identity
from value in Request.Form.GetValues(name) // page responsible for showing the external login confirmation form if necessary.
select new KeyValuePair<string, string>(name, value) : RedirectUri = Url.Action("ExternalLoginCallback", "Account", new
from name in Request.QueryString.AllKeys {
from value in Request.QueryString.GetValues(name) ReturnUrl = Request.RawUrl
select new KeyValuePair<string, string>(name, value) })
}); };
// 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")] // Retrieve the profile of the logged in user.
[HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken] var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId()) ??
public async Task<ActionResult> Accept() throw new InvalidOperationException("The user details cannot be retrieved.");
{
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. // Retrieve the application details from the database.
var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ??
if (result == null || result.Identity == null) throw new InvalidOperationException("Details concerning the calling client application cannot be found.");
{
context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie);
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. switch (await _applicationManager.GetConsentTypeAsync(application))
var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId()) ?? {
throw new InvalidOperationException("The user details cannot be retrieved."); // 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.
// Retrieve the application details from the database. case ConsentTypes.External when authorizations.Count is 0:
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))
{
context.Authentication.Challenge( context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string> properties: new AuthenticationProperties(new Dictionary<string, string>
@ -257,191 +134,313 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
})); }));
return new EmptyResult(); return new EmptyResult();
}
// Create the claims-based identity that will be used by OpenIddict to generate tokens. // If the consent is implicit or if an authorization was found,
var identity = new ClaimsIdentity( // return an authorization response without displaying the consent form.
authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, case ConsentTypes.Implicit:
nameType: Claims.Name, case ConsentTypes.External when authorizations.Count is not 0:
roleType: Claims.Role); 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. // Add the claims that will be persisted in the tokens.
identity.SetClaim(Claims.Subject, user.Id) identity.SetClaim(Claims.Subject, user.Id)
.SetClaim(Claims.Email, user.Email) .SetClaim(Claims.Email, user.Email)
.SetClaim(Claims.Name, user.UserName) .SetClaim(Claims.Name, user.UserName)
.SetClaim(Claims.PreferredUsername, user.UserName) .SetClaim(Claims.PreferredUsername, user.UserName)
.SetClaims(Claims.Role, (await context.Get<ApplicationUserManager>().GetRolesAsync(user.Id)).ToImmutableArray()); .SetClaims(Claims.Role, (await context.Get<ApplicationUserManager>().GetRolesAsync(user.Id)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope // Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes. // but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes. // For that, simply restrict the list of scopes before calling SetScopes.
identity.SetScopes(request.GetScopes()); identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
// Automatically create a permanent authorization to avoid requiring explicit consent // Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes. // for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault(); var authorization = authorizations.LastOrDefault();
authorization ??= await _authorizationManager.CreateAsync( authorization ??= await _authorizationManager.CreateAsync(
identity: identity, identity: identity,
subject : user.Id, subject : user.Id,
client : await _applicationManager.GetIdAsync(application), client : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent, type : AuthorizationTypes.Permanent,
scopes : identity.GetScopes()); scopes : identity.GetScopes());
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations); 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<string, string>
{
[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<string, string>(name, value) :
from name in Request.QueryString.AllKeys
from value in Request.QueryString.GetValues(name)
select new KeyValuePair<string, string>(name, value)
});
} }
}
[Authorize, FormValueRequired("submit.Deny")] [Authorize, FormValueRequired("submit.Accept")]
[HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken] [HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken]
// Notify OpenIddict that the authorization grant has been denied by the resource owner public async Task<ActionResult> Accept()
// to redirect the user agent to the client application using the appropriate response_mode. {
public ActionResult Deny() 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(DefaultAuthenticationTypes.ApplicationCookie);
context.Authentication.Challenge(OpenIddictServerOwinDefaults.AuthenticationType);
return new EmptyResult(); return new EmptyResult();
} }
[HttpGet, Route("~/connect/logout")] // Retrieve the profile of the logged in user.
public ActionResult Logout() => View(new AuthorizeViewModel var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId()) ??
{ throw new InvalidOperationException("The user details cannot be retrieved.");
// Flow the request parameters so they can be received by the Accept/Reject actions.
Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? // Retrieve the application details from the database.
from name in Request.Form.AllKeys var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ??
from value in Request.Form.GetValues(name) throw new InvalidOperationException("Details concerning the calling client application cannot be found.");
select new KeyValuePair<string, string>(name, value) :
from name in Request.QueryString.AllKeys // Retrieve the permanent authorizations associated with the user and the calling client application.
from value in Request.QueryString.GetValues(name) var authorizations = await _authorizationManager.FindAsync(
select new KeyValuePair<string, string>(name, value) subject: user.Id,
}); client : await _applicationManager.GetIdAsync(application),
status : Statuses.Valid,
[ActionName(nameof(Logout)), HttpPost, Route("~/connect/logout"), ValidateAntiForgeryToken] type : AuthorizationTypes.Permanent,
public ActionResult LogoutPost() 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.Challenge(
context.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
context.Authentication.SignOut(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties properties: new AuthenticationProperties(new Dictionary<string, string>
{ {
RedirectUri = "/" [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired,
}); [OpenIddictServerOwinConstants.Properties.ErrorDescription] =
"The logged in user is not allowed to access this client application."
}));
return new EmptyResult(); return new EmptyResult();
} }
[HttpPost, Route("~/connect/token")] // Create the claims-based identity that will be used by OpenIddict to generate tokens.
public async Task<ActionResult> Exchange() var identity = new ClaimsIdentity(
{ authenticationType: OpenIddictServerOwinDefaults.AuthenticationType,
var context = HttpContext.GetOwinContext(); nameType: Claims.Name,
var request = context.GetOpenIddictServerRequest() ?? roleType: Claims.Role);
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// 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<ApplicationUserManager>().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()) [Authorize, FormValueRequired("submit.Deny")]
{ [HttpPost, Route("~/connect/authorize"), ValidateAntiForgeryToken]
// Retrieve the claims identity stored in the authorization code/device code/refresh token. // Notify OpenIddict that the authorization grant has been denied by the resource owner
var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); // 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. return new EmptyResult();
var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetClaim(Claims.Subject)); }
if (user == null)
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
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<string, string>(name, value) :
from name in Request.QueryString.AllKeys
from value in Request.QueryString.GetValues(name)
select new KeyValuePair<string, string>(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. context.Authentication.SignOut(
if (context.GetUserManager<ApplicationUserManager>().IsLockedOut(user.Id)) authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
{ properties: new AuthenticationProperties
context.Authentication.Challenge( {
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, RedirectUri = "/"
properties: new AuthenticationProperties(new Dictionary<string, string> });
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
return new EmptyResult(); return new EmptyResult();
} }
var identity = new ClaimsIdentity(result.Identity.Claims, [HttpPost, Route("~/connect/token")]
authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, public async Task<ActionResult> Exchange()
nameType: Claims.Name, {
roleType: Claims.Role); 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 if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
// changed since the authorization code/refresh token was issued. {
identity.SetClaim(Claims.Subject, user.Id) // Retrieve the claims identity stored in the authorization code/device code/refresh token.
.SetClaim(Claims.Email, user.Email) var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType);
.SetClaim(Claims.Name, user.UserName)
.SetClaim(Claims.PreferredUsername, user.UserName)
.SetClaims(Claims.Role, (await context.Get<ApplicationUserManager>().GetRolesAsync(user.Id)).ToImmutableArray());
identity.SetDestinations(GetDestinations); // Retrieve the user profile corresponding to the authorization code/refresh token.
var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetClaim(Claims.Subject));
if (user == null)
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
// Ask OpenIddict to issue the appropriate access/identity tokens. return new EmptyResult();
context.Authentication.SignIn(new AuthenticationProperties(), identity); }
// Ensure the user is still allowed to sign in.
if (context.GetUserManager<ApplicationUserManager>().IsLockedOut(user.Id))
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
return new EmptyResult(); 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<ApplicationUserManager>().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<string> GetDestinations(Claim claim) throw new InvalidOperationException("The specified grant type is not supported.");
{ }
// 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.
switch (claim.Type) private static IEnumerable<string> GetDestinations(Claim claim)
{ {
case Claims.Name or Claims.PreferredUsername: // Note: by default, claims are NOT automatically included in the access and identity tokens.
yield return Destinations.AccessToken; // 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)) switch (claim.Type)
yield return Destinations.IdentityToken; {
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 break;
yield return Destinations.AccessToken;
if (claim.Subject.HasScope(Scopes.Email)) case Claims.Email:
yield return Destinations.IdentityToken; yield return Destinations.AccessToken;
yield break; if (claim.Subject.HasScope(Scopes.Email))
yield return Destinations.IdentityToken;
case Claims.Role: yield break;
yield return Destinations.AccessToken;
if (claim.Subject.HasScope(Scopes.Roles)) case Claims.Role:
yield return Destinations.IdentityToken; 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. yield break;
case "AspNet.Identity.SecurityStamp": yield break;
default: // Never include the security stamp in the access and identity tokens, as it's a secret value.
yield return Destinations.AccessToken; case "AspNet.Identity.SecurityStamp": yield break;
yield break;
} default:
yield return Destinations.AccessToken;
yield break;
} }
} }
} }

31
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/HomeController.cs

@ -1,26 +1,25 @@
using System.Web.Mvc; 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() public ActionResult About()
{ {
ViewBag.Message = "Your application description page."; ViewBag.Message = "Your application description page.";
return View(); return View();
} }
public ActionResult Contact() public ActionResult Contact()
{ {
ViewBag.Message = "Your contact page."; ViewBag.Message = "Your contact page.";
return View(); return View();
}
} }
} }

567
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ManageController.cs

@ -7,229 +7,258 @@ using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security; using Microsoft.Owin.Security;
using OpenIddict.Sandbox.AspNet.Server.Models; using OpenIddict.Sandbox.AspNet.Server.Models;
namespace OpenIddict.Sandbox.AspNet.Server.Controllers namespace OpenIddict.Sandbox.AspNet.Server.Controllers;
[Authorize]
public class ManageController : Controller
{ {
[Authorize] private ApplicationSignInManager _signInManager;
public class ManageController : Controller private ApplicationUserManager _userManager;
public ManageController()
{
}
public ManageController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
{ {
private ApplicationSignInManager _signInManager; UserManager = userManager;
private ApplicationUserManager _userManager; SignInManager = signInManager;
}
public ManageController() public ApplicationSignInManager SignInManager
{
get
{ {
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
} }
}
public ManageController(ApplicationUserManager userManager, ApplicationSignInManager signInManager) public ApplicationUserManager UserManager
{
get
{ {
UserManager = userManager; return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
SignInManager = signInManager;
} }
private set
public ApplicationSignInManager SignInManager
{ {
get _userManager = value;
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
}
} }
}
public ApplicationUserManager UserManager //
// GET: /Manage/Index
public async Task<ActionResult> 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 HasPassword = HasPassword(),
{ PhoneNumber = await UserManager.GetPhoneNumberAsync(userId),
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>(); TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId),
} Logins = await UserManager.GetLoginsAsync(userId),
private set BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId)
};
return View(model);
}
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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 // GET: /Manage/AddPhoneNumber
public async Task<ActionResult> Index(ManageMessageId? message) public ActionResult AddPhoneNumber()
{
return View();
}
//
// POST: /Manage/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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); return View(model);
} }
// Générer le jeton et l'envoyer
// var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), model.Number);
// POST: /Manage/RemoveLogin if (UserManager.SmsService != null)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RemoveLogin(string loginProvider, string providerKey)
{ {
ManageMessageId? message; var message = new IdentityMessage
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
{ {
message = ManageMessageId.Error; Destination = model.Number,
} Body = "Votre code de sécurité est : " + code
return RedirectToAction("ManageLogins", new { Message = message }); };
await UserManager.SmsService.SendAsync(message);
} }
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}
// //
// GET: /Manage/AddPhoneNumber // POST: /Manage/EnableTwoFactorAuthentication
public ActionResult AddPhoneNumber() [HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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 // POST: /Manage/DisableTwoFactorAuthentication
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model) public async Task<ActionResult> DisableTwoFactorAuthentication()
{
await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{ {
if (!ModelState.IsValid) await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
{
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 });
} }
return RedirectToAction("Index", "Manage");
}
//
// GET: /Manage/VerifyPhoneNumber
public async Task<ActionResult> 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 // POST: /Manage/VerifyPhoneNumber
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<ActionResult> EnableTwoFactorAuthentication() public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{ {
await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true); return View(model);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", "Manage");
} }
var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
// if (result.Succeeded)
// POST: /Manage/DisableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DisableTwoFactorAuthentication()
{ {
await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null) if (user != null)
{ {
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 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 // POST: /Manage/RemovePhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber) [HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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); await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// 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 });
} }
return RedirectToAction("Index", new { Message = ManageMessageId.RemovePhoneSuccess });
}
// //
// POST: /Manage/VerifyPhoneNumber // GET: /Manage/ChangePassword
[HttpPost] public ActionResult ChangePassword()
[ValidateAntiForgeryToken] {
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model) return View();
}
//
// POST: /Manage/ChangePassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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); return View(model);
} }
var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
// if (result.Succeeded)
// POST: /Manage/RemovePhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> 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()); var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null) if (user != null)
{ {
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 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 // GET: /Manage/SetPassword
public ActionResult ChangePassword() public ActionResult SetPassword()
{ {
return View(); return View();
} }
// //
// POST: /Manage/ChangePassword // POST: /Manage/SetPassword
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<ActionResult> ChangePassword(ChangePasswordViewModel model) public async Task<ActionResult> SetPassword(SetPasswordViewModel model)
{
if (ModelState.IsValid)
{ {
if (!ModelState.IsValid) var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
{
return View(model);
}
var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
if (result.Succeeded) if (result.Succeeded)
{ {
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 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); await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
} }
return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess }); return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess });
} }
AddErrors(result); AddErrors(result);
return View(model);
} }
// // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire
// GET: /Manage/SetPassword return View(model);
public ActionResult SetPassword() }
{
return View();
}
// //
// POST: /Manage/SetPassword // GET: /Manage/ManageLogins
[HttpPost] public async Task<ActionResult> ManageLogins(ManageMessageId? message)
[ValidateAntiForgeryToken] {
public async Task<ActionResult> SetPassword(SetPasswordViewModel model) 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) return View("Error");
{
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);
} }
var userLogins = await UserManager.GetLoginsAsync(User.Identity.GetUserId());
// var otherLogins = AuthenticationManager.GetExternalAuthenticationTypes().Where(auth => userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList();
// GET: /Manage/ManageLogins ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1;
public async Task<ActionResult> ManageLogins(ManageMessageId? message) return View(new ManageLoginsViewModel
{ {
ViewBag.StatusMessage = CurrentLogins = userLogins,
message == ManageMessageId.RemoveLoginSuccess ? "La connexion externe a été supprimée." OtherLogins = otherLogins
: 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
});
}
// //
// POST: /Manage/LinkLogin // POST: /Manage/LinkLogin
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider) public ActionResult LinkLogin(string provider)
{ {
// Demander une redirection vers le fournisseur de connexion externe afin de lier une connexion pour l'utilisateur actuel // 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()); return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId());
} }
// //
// GET: /Manage/LinkLoginCallback // GET: /Manage/LinkLoginCallback
public async Task<ActionResult> LinkLoginCallback() public async Task<ActionResult> LinkLoginCallback()
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
if (loginInfo == null)
{ {
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
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 });
} }
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;
_userManager.Dispose();
_userManager = null;
}
base.Dispose(disposing);
} }
base.Dispose(disposing);
}
#region Applications d'assistance #region Applications d'assistance
// Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes // Utilisé(e) pour la protection XSRF lors de l'ajout de connexions externes
private const string XsrfKey = "XsrfId"; 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()); return user.PasswordHash != null;
if (user != null)
{
return user.PasswordHash != null;
}
return false;
} }
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()); return user.PhoneNumber != null;
if (user != null)
{
return user.PhoneNumber != null;
}
return false;
} }
return false;
}
public enum ManageMessageId public enum ManageMessageId
{ {
AddPhoneSuccess, AddPhoneSuccess,
ChangePasswordSuccess, ChangePasswordSuccess,
SetTwoFactorSuccess, SetTwoFactorSuccess,
SetPasswordSuccess, SetPasswordSuccess,
RemoveLoginSuccess, RemoveLoginSuccess,
RemovePhoneSuccess, RemovePhoneSuccess,
Error Error
} }
#endregion #endregion
}
} }

83
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs

@ -10,52 +10,51 @@ using OpenIddict.Abstractions;
using OpenIddict.Validation.Owin; using OpenIddict.Validation.Owin;
using static OpenIddict.Abstractions.OpenIddictConstants; 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)] [Authorize, HttpGet, Route("~/api/message")]
public class ResourceController : ApiController public async Task<IHttpActionResult> GetMessage()
{ {
[Authorize, HttpGet, Route("~/api/message")] var context = Request.GetOwinContext();
public async Task<IHttpActionResult> GetMessage()
{
var context = Request.GetOwinContext();
// This demo action requires that the client application be granted the "demo_api" scope. // 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 // 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. // 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")) if (User is not ClaimsPrincipal principal || !principal.HasScope("demo_api"))
{ {
context.Authentication.Challenge( context.Authentication.Challenge(
authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType, authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string> properties: new AuthenticationProperties(new Dictionary<string, string>
{ {
[OpenIddictValidationOwinConstants.Properties.Scope] = "demo_api", [OpenIddictValidationOwinConstants.Properties.Scope] = "demo_api",
[OpenIddictValidationOwinConstants.Properties.Error] = Errors.InsufficientScope, [OpenIddictValidationOwinConstants.Properties.Error] = Errors.InsufficientScope,
[OpenIddictValidationOwinConstants.Properties.ErrorDescription] = [OpenIddictValidationOwinConstants.Properties.ErrorDescription] =
"The 'demo_api' scope is required to perform this action." "The 'demo_api' scope is required to perform this action."
})); }));
return Unauthorized(); return Unauthorized();
} }
var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(
((ClaimsPrincipal) User).FindFirst(Claims.Subject).Value);
if (user is null)
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[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) var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(
{ ((ClaimsPrincipal) User).FindFirst(Claims.Subject).Value);
Content = new StringContent($"{user.UserName} has been successfully authenticated.") if (user is null)
}); {
context.Authentication.Challenge(
authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[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.")
});
} }
} }

17
sandbox/OpenIddict.Sandbox.AspNet.Server/Global.asax.cs

@ -3,16 +3,15 @@ using System.Web.Mvc;
using System.Web.Optimization; using System.Web.Optimization;
using System.Web.Routing; 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);
AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); BundleConfig.RegisterBundles(BundleTable.Bundles);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
} }
} }

33
sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/AsyncEnumerableExtensions.cs

@ -2,30 +2,29 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; 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<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source)
{ {
public static Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source) if (source == null)
{ {
if (source == null) throw new ArgumentNullException(nameof(source));
{ }
throw new ArgumentNullException(nameof(source));
}
return ExecuteAsync(); return ExecuteAsync();
async Task<List<T>> ExecuteAsync() async Task<List<T>> ExecuteAsync()
{ {
var list = new List<T>(); var list = new List<T>();
await foreach (var element in source)
{
list.Add(element);
}
return list; await foreach (var element in source)
{
list.Add(element);
} }
return list;
} }
} }
} }

47
sandbox/OpenIddict.Sandbox.AspNet.Server/Helpers/FormValueRequiredAttribute.cs

@ -2,38 +2,37 @@ using System;
using System.Reflection; using System.Reflection;
using System.Web.Mvc; 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) || return false;
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 !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]);
} }
} }

213
sandbox/OpenIddict.Sandbox.AspNet.Server/Models/AccountViewModels.cs

@ -1,112 +1,111 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; 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<System.Web.Mvc.SelectListItem> 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]
{ [EmailAddress]
[Required] [Display(Name = "E-mail")]
[Display(Name = "Courrier électronique")] public string Email { get; set; }
public string Email { get; set; }
}
public class ExternalLoginListViewModel
{
public string ReturnUrl { get; set; }
}
public class SendCodeViewModel
{
public string SelectedProvider { get; set; }
public ICollection<System.Web.Mvc.SelectListItem> 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; }
}
} }

47
sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs

@ -4,37 +4,36 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework; 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 async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
public class ApplicationUser : IdentityUser
{ {
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) // Notez que l'authenticationType doit correspondre à celui défini dans CookieAuthenticationOptions.AuthenticationType
{ var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Notez que l'authenticationType doit correspondre à celui défini dans CookieAuthenticationOptions.AuthenticationType // Ajouter des revendications utilisateur personnalisées ici
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); return userIdentity;
// Ajouter des revendications utilisateur personnalisées ici
return userIdentity;
}
} }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{ {
public ApplicationDbContext() }
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create() public static ApplicationDbContext Create()
{ {
return new ApplicationDbContext(); return new ApplicationDbContext();
} }
protected override void OnModelCreating(DbModelBuilder modelBuilder) protected override void OnModelCreating(DbModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
modelBuilder.UseOpenIddict(); modelBuilder.UseOpenIddict();
}
} }
} }

135
sandbox/OpenIddict.Sandbox.AspNet.Server/Models/ManageViewModels.cs

@ -3,84 +3,83 @@ using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security; 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<UserLoginInfo> Logins { get; set; }
public bool HasPassword { get; set; } public string PhoneNumber { get; set; }
public IList<UserLoginInfo> Logins { get; set; } public bool TwoFactor { get; set; }
public string PhoneNumber { get; set; } public bool BrowserRemembered { get; set; }
public bool TwoFactor { get; set; } }
public bool BrowserRemembered { get; set; }
}
public class ManageLoginsViewModel public class ManageLoginsViewModel
{ {
public IList<UserLoginInfo> CurrentLogins { get; set; } public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationDescription> OtherLogins { get; set; } public IList<AuthenticationDescription> OtherLogins { get; set; }
} }
public class FactorViewModel public class FactorViewModel
{ {
public string Purpose { get; set; } public string Purpose { get; set; }
} }
public class SetPasswordViewModel public class SetPasswordViewModel
{ {
[Required] [Required]
[StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)]
[DataType(DataType.Password)] [DataType(DataType.Password)]
[Display(Name = "Nouveau mot de passe")] [Display(Name = "Nouveau mot de passe")]
public string NewPassword { get; set; } public string NewPassword { get; set; }
[DataType(DataType.Password)] [DataType(DataType.Password)]
[Display(Name = "Confirmer le nouveau mot de passe")] [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.")] [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")]
public string ConfirmPassword { get; set; } public string ConfirmPassword { get; set; }
} }
public class ChangePasswordViewModel public class ChangePasswordViewModel
{ {
[Required] [Required]
[DataType(DataType.Password)] [DataType(DataType.Password)]
[Display(Name = "Mot de passe actuel")] [Display(Name = "Mot de passe actuel")]
public string OldPassword { get; set; } public string OldPassword { get; set; }
[Required] [Required]
[StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)]
[DataType(DataType.Password)] [DataType(DataType.Password)]
[Display(Name = "Nouveau mot de passe")] [Display(Name = "Nouveau mot de passe")]
public string NewPassword { get; set; } public string NewPassword { get; set; }
[DataType(DataType.Password)] [DataType(DataType.Password)]
[Display(Name = "Confirmer le nouveau mot de passe")] [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.")] [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")]
public string ConfirmPassword { get; set; } public string ConfirmPassword { get; set; }
} }
public class AddPhoneNumberViewModel public class AddPhoneNumberViewModel
{ {
[Required] [Required]
[Phone] [Phone]
[Display(Name = "Numéro de téléphone")] [Display(Name = "Numéro de téléphone")]
public string Number { get; set; } public string Number { get; set; }
} }
public class VerifyPhoneNumberViewModel public class VerifyPhoneNumberViewModel
{ {
[Required] [Required]
[Display(Name = "Code")] [Display(Name = "Code")]
public string Code { get; set; } public string Code { get; set; }
[Required] [Required]
[Phone] [Phone]
[Display(Name = "Numéro de téléphone")] [Display(Name = "Numéro de téléphone")]
public string PhoneNumber { get; set; } public string PhoneNumber { get; set; }
} }
public class ConfigureTwoFactorViewModel public class ConfigureTwoFactorViewModel
{ {
public string SelectedProvider { get; set; } public string SelectedProvider { get; set; }
public ICollection<System.Web.Mvc.SelectListItem> Providers { get; set; } public ICollection<System.Web.Mvc.SelectListItem> Providers { get; set; }
}
} }

455
sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs

@ -22,257 +22,256 @@ using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Abstractions.OpenIddictConstants;
[assembly: OwinStartup(typeof(OpenIddict.Sandbox.AspNet.Server.Startup))] [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. // Register the OpenIddict core components.
.AddCore(options => .AddCore(options =>
{ {
// Configure OpenIddict to use the Entity Framework 6.x stores and models. // Configure OpenIddict to use the Entity Framework 6.x stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFramework() options.UseEntityFramework()
.UseDbContext<ApplicationDbContext>(); .UseDbContext<ApplicationDbContext>();
// Developers who prefer using MongoDB can remove the previous lines // Developers who prefer using MongoDB can remove the previous lines
// and configure OpenIddict to use the specified MongoDB database: // and configure OpenIddict to use the specified MongoDB database:
// options.UseMongoDb() // options.UseMongoDb()
// .UseDatabase(new MongoClient().GetDatabase("openiddict")); // .UseDatabase(new MongoClient().GetDatabase("openiddict"));
}) })
// Register the OpenIddict client components. // Register the OpenIddict client components.
.AddClient(options => .AddClient(options =>
{ {
// Note: this sample uses the code flow, but you can enable the other flows if necessary. // Note: this sample uses the code flow, but you can enable the other flows if necessary.
options.AllowAuthorizationCodeFlow(); options.AllowAuthorizationCodeFlow();
// Register the signing and encryption credentials used to protect // Register the signing and encryption credentials used to protect
// sensitive data like the state tokens produced by OpenIddict. // sensitive data like the state tokens produced by OpenIddict.
options.AddDevelopmentEncryptionCertificate() options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate(); .AddDevelopmentSigningCertificate();
// Register the OWIN host and configure the OWIN-specific options. // Register the OWIN host and configure the OWIN-specific options.
options.UseOwin() options.UseOwin()
.EnableRedirectionEndpointPassthrough() .EnableRedirectionEndpointPassthrough()
.SetCookieManager(new SystemWebCookieManager()); .SetCookieManager(new SystemWebCookieManager());
// Register the System.Net.Http integration and use the identity of the current // 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 // 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). // providers that use the user agent as a way to throttle requests (e.g Reddit).
options.UseSystemNetHttp() options.UseSystemNetHttp()
.SetProductInformation(typeof(Startup).Assembly); .SetProductInformation(typeof(Startup).Assembly);
// Register the Web providers integrations. // Register the Web providers integrations.
// //
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint // 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" // 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, // 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. // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
options.UseWebProviders() options.UseWebProviders()
.AddGitHub(options => .AddGitHub(options =>
{ {
options.SetClientId("c4ade52327b01ddacff3") options.SetClientId("c4ade52327b01ddacff3")
.SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
.SetRedirectUri("callback/login/github"); .SetRedirectUri("callback/login/github");
}); });
}) })
// Register the OpenIddict server components. // Register the OpenIddict server components.
.AddServer(options => .AddServer(options =>
{ {
// Enable the authorization, device, introspection, // Enable the authorization, device, introspection,
// logout, token, userinfo and verification endpoints. // logout, token, userinfo and verification endpoints.
options.SetAuthorizationEndpointUris("connect/authorize") options.SetAuthorizationEndpointUris("connect/authorize")
.SetDeviceEndpointUris("connect/device") .SetDeviceEndpointUris("connect/device")
.SetIntrospectionEndpointUris("connect/introspect") .SetIntrospectionEndpointUris("connect/introspect")
.SetLogoutEndpointUris("connect/logout") .SetLogoutEndpointUris("connect/logout")
.SetTokenEndpointUris("connect/token") .SetTokenEndpointUris("connect/token")
.SetUserinfoEndpointUris("connect/userinfo") .SetUserinfoEndpointUris("connect/userinfo")
.SetVerificationEndpointUris("connect/verify"); .SetVerificationEndpointUris("connect/verify");
// Note: this sample uses the code, device code, password and refresh token flows, but you // 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. // can enable the other flows if you need to support implicit or client credentials.
options.AllowAuthorizationCodeFlow() options.AllowAuthorizationCodeFlow()
.AllowDeviceCodeFlow() .AllowDeviceCodeFlow()
.AllowPasswordFlow() .AllowPasswordFlow()
.AllowRefreshTokenFlow(); .AllowRefreshTokenFlow();
// Mark the "email", "profile", "roles" and "demo_api" scopes as supported scopes. // Mark the "email", "profile", "roles" and "demo_api" scopes as supported scopes.
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api"); options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api");
// Register the signing and encryption credentials. // Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate() options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate(); .AddDevelopmentSigningCertificate();
// Force client applications to use Proof Key for Code Exchange (PKCE). // Force client applications to use Proof Key for Code Exchange (PKCE).
options.RequireProofKeyForCodeExchange(); options.RequireProofKeyForCodeExchange();
// Register the OWIN host and configure the OWIN-specific options. // Register the OWIN host and configure the OWIN-specific options.
options.UseOwin() options.UseOwin()
.EnableAuthorizationEndpointPassthrough() .EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough() .EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough(); .EnableTokenEndpointPassthrough();
}) })
// Register the OpenIddict validation components. // Register the OpenIddict validation components.
.AddValidation(options => .AddValidation(options =>
{ {
// Import the configuration from the local OpenIddict server instance. // Import the configuration from the local OpenIddict server instance.
options.UseLocalServer(); options.UseLocalServer();
// Register the OWIN host. // Register the OWIN host.
options.UseOwin(); options.UseOwin();
}); });
// Create a new Autofac container and import the OpenIddict services. // Create a new Autofac container and import the OpenIddict services.
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.Populate(services); builder.Populate(services);
// Register the MVC controllers. // Register the MVC controllers.
builder.RegisterControllers(typeof(Startup).Assembly); builder.RegisterControllers(typeof(Startup).Assembly);
// Register the Web API controllers. // Register the Web API controllers.
builder.RegisterApiControllers(typeof(Startup).Assembly); builder.RegisterApiControllers(typeof(Startup).Assembly);
var container = builder.Build(); var container = builder.Build();
// Register the Autofac scope injector middleware. // Register the Autofac scope injector middleware.
app.UseAutofacLifetimeScopeInjector(container); app.UseAutofacLifetimeScopeInjector(container);
// Register the Entity Framework context and the user/sign-in managers used by ASP.NET Identity. // Register the Entity Framework context and the user/sign-in managers used by ASP.NET Identity.
app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Register the cookie middleware used by ASP.NET Identity. // Register the cookie middleware used by ASP.NET Identity.
app.UseCookieAuthentication(new CookieAuthenticationOptions app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{ {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
LoginPath = new PathString("/Account/Login"), validateInterval: TimeSpan.FromMinutes(30),
Provider = new CookieAuthenticationProvider regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
{ }
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( });
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);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Register the OpenIddict middleware.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); app.UseMiddlewareFromContainer<OpenIddictClientOwinMiddleware>();
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); app.UseMiddlewareFromContainer<OpenIddictServerOwinMiddleware>();
app.UseMiddlewareFromContainer<OpenIddictValidationOwinMiddleware>();
// 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. configuration.MapHttpAttributeRoutes();
app.UseMiddlewareFromContainer<OpenIddictClientOwinMiddleware>(); configuration.SuppressDefaultHostAuthentication();
app.UseMiddlewareFromContainer<OpenIddictServerOwinMiddleware>();
app.UseMiddlewareFromContainer<OpenIddictValidationOwinMiddleware>();
// Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances. // Register the Autofac Web API integration and Web API middleware.
DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); app.UseAutofacWebApi(configuration);
app.UseWebApi(configuration);
// Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances // Seed the database with the sample client using the OpenIddict application manager.
// and infer the Web API routes using the HTTP attributes used in the controllers. // Note: in a real world application, this step should be part of a setup script.
var configuration = new HttpConfiguration Task.Run(async delegate
{ {
DependencyResolver = new AutofacWebApiDependencyResolver(container) await using var scope = container.BeginLifetimeScope();
};
configuration.MapHttpAttributeRoutes(); var context = scope.Resolve<ApplicationDbContext>();
configuration.SuppressDefaultHostAuthentication(); context.Database.CreateIfNotExists();
// Register the Autofac Web API integration and Web API middleware. var manager = scope.Resolve<IOpenIddictApplicationManager>();
app.UseAutofacWebApi(configuration);
app.UseWebApi(configuration);
// Seed the database with the sample client using the OpenIddict application manager. if (await manager.FindByClientIdAsync("mvc") is null)
// 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(); await manager.CreateAsync(new OpenIddictApplicationDescriptor
var context = scope.Resolve<ApplicationDbContext>();
context.Database.CreateIfNotExists();
var manager = scope.Resolve<IOpenIddictApplicationManager>();
if (await manager.FindByClientIdAsync("mvc") is null)
{ {
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, Permissions.Endpoints.Authorization,
ClientId = "mvc", Permissions.Endpoints.Logout,
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", Permissions.Endpoints.Token,
ClientType = ClientTypes.Confidential, Permissions.GrantTypes.AuthorizationCode,
ConsentType = ConsentTypes.Explicit, Permissions.GrantTypes.RefreshToken,
DisplayName = "MVC client application", Permissions.ResponseTypes.Code,
RedirectUris = Permissions.Scopes.Email,
{ Permissions.Scopes.Profile,
new Uri("https://localhost:44378/callback/login/local") Permissions.Scopes.Roles,
}, Permissions.Prefixes.Scope + "demo_api"
PostLogoutRedirectUris = },
{ Requirements =
new Uri("https://localhost:44378/callback/logout/local") {
}, Requirements.Features.ProofKeyForCodeExchange
Permissions = }
{ });
Permissions.Endpoints.Authorization, }
Permissions.Endpoints.Logout,
Permissions.Endpoints.Token, if (await manager.FindByClientIdAsync("postman") is null)
Permissions.GrantTypes.AuthorizationCode, {
Permissions.GrantTypes.RefreshToken, await manager.CreateAsync(new OpenIddictApplicationDescriptor
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 ApplicationType = ApplicationTypes.Native,
ClientId = "postman",
ClientType = ClientTypes.Public,
ConsentType = ConsentTypes.Systematic,
DisplayName = "Postman",
RedirectUris =
{ {
ApplicationType = ApplicationTypes.Native, new Uri("https://oauth.pstmn.io/v1/callback")
ClientId = "postman", },
ClientType = ClientTypes.Public, Permissions =
ConsentType = ConsentTypes.Systematic, {
DisplayName = "Postman", Permissions.Endpoints.Authorization,
RedirectUris = Permissions.Endpoints.Device,
{ Permissions.Endpoints.Token,
new Uri("https://oauth.pstmn.io/v1/callback") Permissions.GrantTypes.AuthorizationCode,
}, Permissions.GrantTypes.DeviceCode,
Permissions = Permissions.GrantTypes.Password,
{ Permissions.GrantTypes.RefreshToken,
Permissions.Endpoints.Authorization, Permissions.ResponseTypes.Code,
Permissions.Endpoints.Device, Permissions.Scopes.Email,
Permissions.Endpoints.Token, Permissions.Scopes.Profile,
Permissions.GrantTypes.AuthorizationCode, Permissions.Scopes.Roles
Permissions.GrantTypes.DeviceCode, },
Permissions.GrantTypes.Password, Settings =
Permissions.GrantTypes.RefreshToken, {
Permissions.ResponseTypes.Code, // Use a shorter access token lifetime for tokens issued to the Postman application.
Permissions.Scopes.Email, [Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture)
Permissions.Scopes.Profile, }
Permissions.Scopes.Roles });
}, }
Settings = }).GetAwaiter().GetResult();
{
// 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();
}
} }
} }

11
sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/AuthorizeViewModel.cs

@ -1,11 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Web.Mvc; 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 IEnumerable<KeyValuePair<string, string>> Parameters { get; internal set; }
public class LogoutViewModel
{
public IEnumerable<KeyValuePair<string, string>> Parameters { get; internal set; }
}
} }

19
sandbox/OpenIddict.Sandbox.AspNet.Server/ViewModels/Authorization/LogoutViewModel.cs

@ -2,17 +2,16 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Web.Mvc; 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))] [Display(Name = "Application")]
public class AuthorizeViewModel public string ApplicationName { get; set; }
{
[Display(Name = "Application")]
public string ApplicationName { get; set; }
[Display(Name = "Scope")] [Display(Name = "Scope")]
public string Scope { get; set; } public string Scope { get; set; }
public IEnumerable<KeyValuePair<string, string>> Parameters { get; internal set; } public IEnumerable<KeyValuePair<string, string>> Parameters { get; internal set; }
}
} }

Loading…
Cancel
Save