From 5899533ae7028a30cb126de38713b7d8793ca653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Tue, 23 Feb 2016 18:56:40 +0100 Subject: [PATCH] Introduce OpenIddictConfiguration/OpenIddictServices, decouple OpenIddictStore from UserStore and add new extensions --- src/OpenIddict.Core/IOpenIddictStore.cs | 3 +- .../OpenIddictConfiguration.cs | 36 +++++++++ src/OpenIddict.Core/OpenIddictExtensions.cs | 45 ++++++++++- src/OpenIddict.Core/OpenIddictHelpers.cs | 32 +++++++- src/OpenIddict.Core/OpenIddictManager.cs | 75 +++++++------------ .../OpenIddictProvider.Authentication.cs | 20 ++--- .../OpenIddictProvider.Exchange.cs | 50 ++++++------- .../OpenIddictProvider.Introspection.cs | 16 ++-- .../OpenIddictProvider.Session.cs | 4 +- .../OpenIddictProvider.Userinfo.cs | 28 +++---- src/OpenIddict.Core/OpenIddictServices.cs | 54 ++++++++++--- src/OpenIddict.EF/OpenIddictExtensions.cs | 57 +++++++++----- src/OpenIddict.EF/OpenIddictStore.cs | 12 ++- src/OpenIddict.Mvc/OpenIddictController.cs | 39 ++++------ src/OpenIddict.Mvc/OpenIddictExtensions.cs | 41 ++++++---- 15 files changed, 328 insertions(+), 184 deletions(-) create mode 100644 src/OpenIddict.Core/OpenIddictConfiguration.cs diff --git a/src/OpenIddict.Core/IOpenIddictStore.cs b/src/OpenIddict.Core/IOpenIddictStore.cs index 0ec084c7..b8a2016d 100644 --- a/src/OpenIddict.Core/IOpenIddictStore.cs +++ b/src/OpenIddict.Core/IOpenIddictStore.cs @@ -1,9 +1,8 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; namespace OpenIddict { - public interface IOpenIddictStore : IUserStore where TUser : class where TApplication : class { + public interface IOpenIddictStore where TApplication : class { Task FindApplicationByIdAsync(string identifier, CancellationToken cancellationToken); Task FindApplicationByLogoutRedirectUri(string url, CancellationToken cancellationToken); Task GetApplicationTypeAsync(TApplication application, CancellationToken cancellationToken); diff --git a/src/OpenIddict.Core/OpenIddictConfiguration.cs b/src/OpenIddict.Core/OpenIddictConfiguration.cs new file mode 100644 index 00000000..7a89292b --- /dev/null +++ b/src/OpenIddict.Core/OpenIddictConfiguration.cs @@ -0,0 +1,36 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace OpenIddict { + public class OpenIddictConfiguration { + public OpenIddictConfiguration(IServiceCollection services) { + Services = services; + } + + /// + /// Gets or sets the type corresponding to the Application entity. + /// + public Type ApplicationType { get; set; } + + /// + /// Gets or sets the type corresponding to the Role entity. + /// + public Type RoleType { get; set; } + + /// + /// Gets or sets the type corresponding to the User entity. + /// + public Type UserType { get; set; } + + /// + /// Gets the services used by OpenIddict. + /// + public IServiceCollection Services { get; } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 2dd7d577..a8201bf5 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictExtensions.cs @@ -6,6 +6,7 @@ using System; using System.Linq; +using System.Reflection; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.AspNetCore.Hosting; @@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.Builder { public static class OpenIddictExtensions { public static IdentityBuilder AddOpenIddictCore( [NotNull] this IdentityBuilder builder, - [NotNull] Action configuration) + [NotNull] Action configuration) where TApplication : class { if (builder == null) { throw new ArgumentNullException(nameof(builder)); @@ -40,19 +41,55 @@ namespace Microsoft.AspNetCore.Builder { typeof(OpenIddictManager<,>).MakeGenericType( builder.UserType, typeof(TApplication))); - var services = new OpenIddictServices(builder.Services) { + builder.Services.TryAddTransient( + typeof(OpenIddictServices<,>).MakeGenericType( + builder.UserType, typeof(TApplication))); + + var instance = new OpenIddictConfiguration(builder.Services) { ApplicationType = typeof(TApplication), RoleType = builder.RoleType, UserType = builder.UserType }; - builder.Services.TryAddSingleton(services); + builder.Services.TryAddSingleton(instance); - configuration(services); + configuration(instance); return builder; } + public static OpenIddictConfiguration UseManager([NotNull] this OpenIddictConfiguration configuration) { + if (configuration == null) { + throw new ArgumentNullException(nameof(configuration)); + } + + var contract = typeof(OpenIddictManager<,>).MakeGenericType(configuration.UserType, + configuration.ApplicationType); + if (!contract.IsAssignableFrom(typeof(TManager))) { + throw new InvalidOperationException("Custom managers must be derived from OpenIddictManager."); + } + + configuration.Services.Replace(ServiceDescriptor.Scoped(contract, typeof(TManager))); + + return configuration; + } + + public static OpenIddictConfiguration UseStore([NotNull] this OpenIddictConfiguration configuration) { + if (configuration == null) { + throw new ArgumentNullException(nameof(configuration)); + } + + var contract = typeof(IOpenIddictStore<,>).MakeGenericType(configuration.UserType, + configuration.ApplicationType); + if (!contract.IsAssignableFrom(typeof(TStore))) { + throw new InvalidOperationException("Custom stores must implement IOpenIddictStore."); + } + + configuration.Services.Replace(ServiceDescriptor.Scoped(contract, typeof(TStore))); + + return configuration; + } + public static OpenIddictBuilder AddModule( [NotNull] this OpenIddictBuilder builder, [NotNull] string name, int position, diff --git a/src/OpenIddict.Core/OpenIddictHelpers.cs b/src/OpenIddict.Core/OpenIddictHelpers.cs index 727d25a6..05e127aa 100644 --- a/src/OpenIddict.Core/OpenIddictHelpers.cs +++ b/src/OpenIddict.Core/OpenIddictHelpers.cs @@ -1,10 +1,12 @@ using System; using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; namespace OpenIddict { public static class OpenIddictHelpers { public static async Task IsConfidentialApplicationAsync( - this OpenIddictManager manager, TApplication application) + [NotNull] this OpenIddictManager manager, [NotNull] TApplication application) where TUser : class where TApplication : class { if (manager == null) { @@ -21,7 +23,7 @@ namespace OpenIddict { } public static async Task IsPublicApplicationAsync( - this OpenIddictManager manager, TApplication application) + [NotNull] this OpenIddictManager manager, [NotNull] TApplication application) where TUser : class where TApplication : class { if (manager == null) { @@ -36,5 +38,31 @@ namespace OpenIddict { return string.Equals(type, OpenIddictConstants.ApplicationTypes.Public, StringComparison.OrdinalIgnoreCase); } + + public static async Task FindClaimAsync( + [NotNull] this UserManager manager, + [NotNull] TUser user, [NotNull] string type) where TUser : class { + if (manager == null) { + throw new ArgumentNullException(nameof(manager)); + } + + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(type)) { + throw new ArgumentNullException(nameof(type)); + } + + // Note: GetClaimsAsync will automatically throw an exception + // if the underlying store doesn't support custom claims. + + var claims = await manager.GetClaimsAsync(user); + if (claims.Count != 0) { + return claims[0]?.Value; + } + + return null; + } } } diff --git a/src/OpenIddict.Core/OpenIddictManager.cs b/src/OpenIddict.Core/OpenIddictManager.cs index a5eff439..d7a60e31 100644 --- a/src/OpenIddict.Core/OpenIddictManager.cs +++ b/src/OpenIddict.Core/OpenIddictManager.cs @@ -7,54 +7,48 @@ using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Server; using CryptoHelper; +using JetBrains.Annotations; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace OpenIddict { - public class OpenIddictManager : UserManager where TUser : class where TApplication : class { - public OpenIddictManager( - IOpenIddictStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - ILookupNormalizer keyNormalizer, - IdentityErrorDescriber errors, - IServiceProvider services, - ILogger> logger) - : base(store, optionsAccessor, - passwordHasher, userValidators, - passwordValidators, keyNormalizer, - errors, services, logger) { - Context = services.GetService()?.HttpContext; - Options = optionsAccessor.Value; + public class OpenIddictManager where TUser : class where TApplication : class { + public OpenIddictManager([NotNull] OpenIddictServices services) { + Services = services; } /// /// Gets the HTTP context associated with the current manager. /// - public virtual HttpContext Context { get; } + protected virtual HttpContext Context => Services.Context; /// /// Gets the cancellation token used to abort async operations. /// - public virtual CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; + protected virtual CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; /// - /// Gets the Identity options associated with the current manager. + /// Gets the logger associated with the current manager. /// - public virtual IdentityOptions Options { get; } + protected virtual ILogger Logger => Services.Logger; + + /// + /// Gets the options associated with the current manager. + /// + protected virtual IdentityOptions Options => Services.Services.GetRequiredService>().Value; + + /// + /// Gets the servuces associated with the current manager. + /// + protected virtual OpenIddictServices Services { get; } /// /// Gets the store associated with the current manager. /// - public virtual new IOpenIddictStore Store { - get { return base.Store as IOpenIddictStore; } - } + protected virtual IOpenIddictStore Store => Services.Store; public virtual async Task CreateIdentityAsync(TUser user, IEnumerable scopes) { if (user == null) { @@ -72,11 +66,11 @@ namespace OpenIddict { // Note: the name identifier is always included in both identity and // access tokens, even if an explicit destination is not specified. - identity.AddClaim(ClaimTypes.NameIdentifier, await GetUserIdAsync(user)); + identity.AddClaim(ClaimTypes.NameIdentifier, await Services.Users.GetUserIdAsync(user)); // Resolve the username and the email address associated with the user. - var username = await GetUserNameAsync(user); - var email = await GetEmailAsync(user); + var username = await Services.Users.GetUserNameAsync(user); + var email = await Services.Users.GetEmailAsync(user); // Only add the name claim if the "profile" scope was granted. if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { @@ -99,16 +93,16 @@ namespace OpenIddict { OpenIdConnectConstants.Destinations.IdentityToken); } - if (SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { - foreach (var role in await GetRolesAsync(user)) { + if (Services.Users.SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { + foreach (var role in await Services.Users.GetRolesAsync(user)) { identity.AddClaim(identity.RoleClaimType, role, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } } - if (SupportsUserSecurityStamp) { - var identifier = await GetSecurityStampAsync(user); + if (Services.Users.SupportsUserSecurityStamp) { + var identifier = await Services.Users.GetSecurityStampAsync(user); if (!string.IsNullOrEmpty(identifier)) { identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, identifier, @@ -128,23 +122,6 @@ namespace OpenIddict { return Store.FindApplicationByLogoutRedirectUri(url, CancellationToken); } - public virtual async Task FindClaimAsync(TUser user, string type) { - if (user == null) { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(type)) { - throw new ArgumentNullException(nameof(type)); - } - - // Note: GetClaimsAsync will automatically throw an exception - // if the underlying store doesn't support custom claims. - - return (from claim in await GetClaimsAsync(user) - where string.Equals(claim.Type, type, StringComparison.Ordinal) - select claim.Value).FirstOrDefault(); - } - public virtual async Task GetApplicationTypeAsync(TApplication application) { if (application == null) { throw new ArgumentNullException(nameof(application)); diff --git a/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs index d88b8fb8..d5f224f2 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs @@ -20,7 +20,7 @@ using Microsoft.Extensions.DependencyInjection; namespace OpenIddict { public partial class OpenIddictProvider : OpenIdConnectServerProvider where TUser : class where TApplication : class { public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: redirect_uri is not required for pure OAuth2 requests // but this provider uses a stricter policy making it mandatory, @@ -35,7 +35,7 @@ namespace OpenIddict { } // Retrieve the application details corresponding to the requested client_id. - var application = await manager.FindApplicationByIdAsync(context.ClientId); + var application = await services.Applications.FindApplicationByIdAsync(context.ClientId); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, @@ -44,7 +44,7 @@ namespace OpenIddict { return; } - if (!await manager.ValidateRedirectUriAsync(application, context.RedirectUri)) { + if (!await services.Applications.ValidateRedirectUriAsync(application, context.RedirectUri)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid redirect_uri."); @@ -56,7 +56,7 @@ namespace OpenIddict { // flow are rejected if the client identifier corresponds to a confidential application. // Note: when using the authorization code grant, ValidateClientAuthentication is responsible of // rejecting the token request if the client_id corresponds to an unauthenticated confidential client. - if (await manager.IsConfidentialApplicationAsync(application) && !context.Request.IsAuthorizationCodeFlow()) { + if (await services.Applications.IsConfidentialApplicationAsync(application) && !context.Request.IsAuthorizationCodeFlow()) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Confidential clients can only use response_type=code."); @@ -68,7 +68,7 @@ namespace OpenIddict { // the appropriate set of scopes is requested to prevent personal data leakage. if (context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) { // Ensure the user profile still exists in the database. - var user = await manager.GetUserAsync(context.HttpContext.User); + var user = await services.Users.GetUserAsync(context.HttpContext.User); if (user == null) { context.Reject( error: OpenIdConnectConstants.Errors.ServerError, @@ -81,8 +81,8 @@ namespace OpenIddict { // email address and if the "email" scope has not been requested. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) && - string.Equals(await manager.GetUserNameAsync(user), - await manager.GetEmailAsync(user), + string.Equals(await services.Users.GetUserNameAsync(user), + await services.Users.GetEmailAsync(user), StringComparison.OrdinalIgnoreCase)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, @@ -136,7 +136,7 @@ namespace OpenIddict { return; } - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: principal is guaranteed to be non-null since ValidateAuthorizationRequest // rejects prompt=none requests missing or having an invalid id_token_hint. @@ -147,7 +147,7 @@ namespace OpenIddict { // the initial check made by ValidateAuthorizationRequest. // In this case, ignore the prompt=none request and // continue to the next middleware in the pipeline. - var user = await manager.GetUserAsync(principal); + var user = await services.Users.GetUserAsync(principal); if (user == null) { return; } @@ -155,7 +155,7 @@ namespace OpenIddict { // Note: filtering the username is not needed at this stage as OpenIddictController.Accept // and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // don't include the "email" scope if the username corresponds to the registed email address. - var identity = await manager.CreateIdentityAsync(user, context.Request.GetScopes()); + var identity = await services.Applications.CreateIdentityAsync(user, context.Request.GetScopes()); Debug.Assert(identity != null); // Create a new authentication ticket holding the user identity. diff --git a/src/OpenIddict.Core/OpenIddictProvider.Exchange.cs b/src/OpenIddict.Core/OpenIddictProvider.Exchange.cs index f41f9292..f9b9d904 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Exchange.cs @@ -20,7 +20,7 @@ using Microsoft.Extensions.Options; namespace OpenIddict { public partial class OpenIddictProvider : OpenIdConnectServerProvider where TUser : class where TApplication : class { public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: OpenIdConnectServerHandler supports authorization code, refresh token, // client credentials, resource owner password credentials and custom grants @@ -53,7 +53,7 @@ namespace OpenIddict { } // Retrieve the application details corresponding to the requested client_id. - var application = await manager.FindApplicationByIdAsync(context.ClientId); + var application = await services.Applications.FindApplicationByIdAsync(context.ClientId); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, @@ -63,7 +63,7 @@ namespace OpenIddict { } // Reject tokens requests containing a client_secret if the client application is not confidential. - if (await manager.IsPublicApplicationAsync(application) && !string.IsNullOrEmpty(context.ClientSecret)) { + if (await services.Applications.IsPublicApplicationAsync(application) && !string.IsNullOrEmpty(context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Public clients are not allowed to send a client_secret."); @@ -73,7 +73,7 @@ namespace OpenIddict { // Confidential applications MUST authenticate // to protect them from impersonation attacks. - else if (await manager.IsConfidentialApplicationAsync(application)) { + else if (await services.Applications.IsConfidentialApplicationAsync(application)) { if (string.IsNullOrEmpty(context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, @@ -82,7 +82,7 @@ namespace OpenIddict { return; } - if (!await manager.ValidateSecretAsync(application, context.ClientSecret)) { + if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid credentials: ensure that you specified a correct client_secret."); @@ -95,10 +95,10 @@ namespace OpenIddict { } public override async Task GrantClientCredentials([NotNull] GrantClientCredentialsContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Retrieve the application details corresponding to the requested client_id. - var application = await manager.FindApplicationByIdAsync(context.ClientId); + var application = await services.Applications.FindApplicationByIdAsync(context.ClientId); Debug.Assert(application != null); var identity = new ClaimsIdentity(context.Options.AuthenticationScheme); @@ -107,7 +107,7 @@ namespace OpenIddict { // access tokens, even if an explicit destination is not specified. identity.AddClaim(ClaimTypes.NameIdentifier, context.ClientId); - identity.AddClaim(ClaimTypes.Name, await manager.GetDisplayNameAsync(application), + identity.AddClaim(ClaimTypes.Name, await services.Applications.GetDisplayNameAsync(application), OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); @@ -125,13 +125,13 @@ namespace OpenIddict { } public override async Task GrantRefreshToken([NotNull] GrantRefreshTokenContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); var options = context.HttpContext.RequestServices.GetRequiredService>(); var principal = context.Ticket?.Principal; Debug.Assert(principal != null); - var user = await manager.GetUserAsync(principal); + var user = await services.Users.GetUserAsync(principal); if (user == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, @@ -142,10 +142,10 @@ namespace OpenIddict { // If the user manager supports security stamps, // ensure that the refresh token is still valid. - if (manager.SupportsUserSecurityStamp) { + if (services.Users.SupportsUserSecurityStamp) { var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType); if (!string.IsNullOrEmpty(identifier) && - !string.Equals(identifier, await manager.GetSecurityStampAsync(user), StringComparison.Ordinal)) { + !string.Equals(identifier, await services.Users.GetSecurityStampAsync(user), StringComparison.Ordinal)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "The refresh token is no longer valid."); @@ -156,7 +156,7 @@ namespace OpenIddict { // Note: the "scopes" property stored in context.AuthenticationTicket is automatically // updated by ASOS when the client application requests a restricted scopes collection. - var identity = await manager.CreateIdentityAsync(user, context.Ticket.GetScopes()); + var identity = await services.Applications.CreateIdentityAsync(user, context.Ticket.GetScopes()); Debug.Assert(identity != null); // Create a new authentication ticket holding the user identity but @@ -170,9 +170,9 @@ namespace OpenIddict { } public override async Task GrantResourceOwnerCredentials([NotNull] GrantResourceOwnerCredentialsContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); - var user = await manager.FindByNameAsync(context.UserName); + var user = await services.Users.FindByNameAsync(context.UserName); if (user == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, @@ -182,7 +182,7 @@ namespace OpenIddict { } // Ensure the user is not already locked out. - if (manager.SupportsUserLockout && await manager.IsLockedOutAsync(user)) { + if (services.Users.SupportsUserLockout && await services.Users.IsLockedOutAsync(user)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "Account locked out."); @@ -191,16 +191,16 @@ namespace OpenIddict { } // Ensure the password is valid. - if (!await manager.CheckPasswordAsync(user, context.Password)) { + if (!await services.Users.CheckPasswordAsync(user, context.Password)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "Invalid credentials."); - if (manager.SupportsUserLockout) { - await manager.AccessFailedAsync(user); + if (services.Users.SupportsUserLockout) { + await services.Users.AccessFailedAsync(user); // Ensure the user is not locked out. - if (await manager.IsLockedOutAsync(user)) { + if (await services.Users.IsLockedOutAsync(user)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "Account locked out."); @@ -210,16 +210,16 @@ namespace OpenIddict { return; } - if (manager.SupportsUserLockout) { - await manager.ResetAccessFailedCountAsync(user); + if (services.Users.SupportsUserLockout) { + await services.Users.ResetAccessFailedCountAsync(user); } // Return an error if the username corresponds to the registered // email address and if the "email" scope has not been requested. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) && - string.Equals(await manager.GetUserNameAsync(user), - await manager.GetEmailAsync(user), + string.Equals(await services.Users.GetUserNameAsync(user), + await services.Users.GetEmailAsync(user), StringComparison.OrdinalIgnoreCase)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, @@ -228,7 +228,7 @@ namespace OpenIddict { return; } - var identity = await manager.CreateIdentityAsync(user, context.Request.GetScopes()); + var identity = await services.Applications.CreateIdentityAsync(user, context.Request.GetScopes()); Debug.Assert(identity != null); // Create a new authentication ticket holding the user identity. diff --git a/src/OpenIddict.Core/OpenIddictProvider.Introspection.cs b/src/OpenIddict.Core/OpenIddictProvider.Introspection.cs index 37c7be7d..4f4f5dd3 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Introspection.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Introspection.cs @@ -17,7 +17,7 @@ using Microsoft.Extensions.Options; namespace OpenIddict { public partial class OpenIddictProvider : OpenIdConnectServerProvider where TUser : class where TApplication : class { public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Note: ASOS supports both GET and POST introspection requests but OpenIddict only accepts POST requests. if (!string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { @@ -41,7 +41,7 @@ namespace OpenIddict { } // Retrieve the application details corresponding to the requested client_id. - var application = await manager.FindApplicationByIdAsync(context.ClientId); + var application = await services.Applications.FindApplicationByIdAsync(context.ClientId); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, @@ -51,7 +51,7 @@ namespace OpenIddict { } // Reject non-confidential applications. - if (await manager.IsPublicApplicationAsync(application)) { + if (await services.Applications.IsPublicApplicationAsync(application)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Public applications are not allowed to use the introspection endpoint."); @@ -60,7 +60,7 @@ namespace OpenIddict { } // Validate the client credentials. - if (!await manager.ValidateSecretAsync(application, context.ClientSecret)) { + if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid credentials: ensure that you specified a correct client_secret."); @@ -72,19 +72,19 @@ namespace OpenIddict { } public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); var options = context.HttpContext.RequestServices.GetRequiredService>(); // If the user manager doesn't support security // stamps, skip the additional validation logic. - if (!manager.SupportsUserSecurityStamp) { + if (!services.Users.SupportsUserSecurityStamp) { return; } var principal = context.Ticket?.Principal; Debug.Assert(principal != null); - var user = await manager.GetUserAsync(principal); + var user = await services.Users.GetUserAsync(principal); if (user == null) { context.Active = false; @@ -93,7 +93,7 @@ namespace OpenIddict { var identifier = principal.GetClaim(options.Value.ClaimsIdentity.SecurityStampClaimType); if (!string.IsNullOrEmpty(identifier) && - !string.Equals(identifier, await manager.GetSecurityStampAsync(user), StringComparison.Ordinal)) { + !string.Equals(identifier, await services.Users.GetSecurityStampAsync(user), StringComparison.Ordinal)) { context.Active = false; return; diff --git a/src/OpenIddict.Core/OpenIddictProvider.Session.cs b/src/OpenIddict.Core/OpenIddictProvider.Session.cs index 478a593f..26f34cd5 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Session.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Session.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.DependencyInjection; namespace OpenIddict { public partial class OpenIddictProvider : OpenIdConnectServerProvider where TUser : class where TApplication : class { public override async Task ValidateLogoutRequest([NotNull] ValidateLogoutRequestContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); // Skip validation if the optional post_logout_redirect_uri // parameter was missing from the logout request. @@ -23,7 +23,7 @@ namespace OpenIddict { return; } - var application = await manager.FindApplicationByLogoutRedirectUri(context.PostLogoutRedirectUri); + var application = await services.Applications.FindApplicationByLogoutRedirectUri(context.PostLogoutRedirectUri); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, diff --git a/src/OpenIddict.Core/OpenIddictProvider.Userinfo.cs b/src/OpenIddict.Core/OpenIddictProvider.Userinfo.cs index 05c59bde..cc1ae11f 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Userinfo.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Userinfo.cs @@ -16,14 +16,14 @@ using Newtonsoft.Json.Linq; namespace OpenIddict { public partial class OpenIddictProvider : OpenIdConnectServerProvider where TUser : class where TApplication : class { public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) { - var manager = context.HttpContext.RequestServices.GetRequiredService>(); + var services = context.HttpContext.RequestServices.GetRequiredService>(); var principal = context.Ticket?.Principal; Debug.Assert(principal != null); // Note: user may be null if the user has been removed. // In this case, return a 400 response. - var user = await manager.GetUserAsync(principal); + var user = await services.Users.GetUserAsync(principal); if (user == null) { context.Response.StatusCode = 400; context.HandleResponse(); @@ -33,47 +33,47 @@ namespace OpenIddict { // Note: "sub" is a mandatory claim. // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse - context.Subject = await manager.GetUserIdAsync(user); + context.Subject = await services.Users.GetUserIdAsync(user); // Only add the "preferred_username" claim if the "profile" scope was present in the access token. // Note: filtering the username is not needed at this stage as OpenIddictController.Accept // and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that // don't include the "email" scope if the username corresponds to the registed email address. if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) { - context.PreferredUsername = await manager.GetUserNameAsync(user); + context.PreferredUsername = await services.Users.GetUserNameAsync(user); - if (manager.SupportsUserClaim) { - context.FamilyName = await manager.FindClaimAsync(user, ClaimTypes.Surname); - context.GivenName = await manager.FindClaimAsync(user, ClaimTypes.GivenName); - context.BirthDate = await manager.FindClaimAsync(user, ClaimTypes.DateOfBirth); + if (services.Users.SupportsUserClaim) { + context.FamilyName = await services.Users.FindClaimAsync(user, ClaimTypes.Surname); + context.GivenName = await services.Users.FindClaimAsync(user, ClaimTypes.GivenName); + context.BirthDate = await services.Users.FindClaimAsync(user, ClaimTypes.DateOfBirth); } } // Only add the email address details if the "email" scope was present in the access token. if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) { - context.Email = await manager.GetEmailAsync(user); + context.Email = await services.Users.GetEmailAsync(user); // Only add the "email_verified" claim // if the email address is non-null. if (!string.IsNullOrEmpty(context.Email)) { - context.EmailVerified = await manager.IsEmailConfirmedAsync(user); + context.EmailVerified = await services.Users.IsEmailConfirmedAsync(user); } }; // Only add the phone number details if the "phone" scope was present in the access token. if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) { - context.PhoneNumber = await manager.GetPhoneNumberAsync(user); + context.PhoneNumber = await services.Users.GetPhoneNumberAsync(user); // Only add the "phone_number_verified" // claim if the phone number is non-null. if (!string.IsNullOrEmpty(context.PhoneNumber)) { - context.PhoneNumberVerified = await manager.IsPhoneNumberConfirmedAsync(user); + context.PhoneNumberVerified = await services.Users.IsPhoneNumberConfirmedAsync(user); } } // Only add the roles list if the "roles" scope was present in the access token. - if (manager.SupportsUserRole && context.Ticket.HasScope(OpenIddictConstants.Scopes.Roles)) { - var roles = await manager.GetRolesAsync(user); + if (services.Users.SupportsUserRole && context.Ticket.HasScope(OpenIddictConstants.Scopes.Roles)) { + var roles = await services.Users.GetRolesAsync(user); if (roles.Count != 0) { context.Claims[OpenIddictConstants.Claims.Roles] = JArray.FromObject(roles); } diff --git a/src/OpenIddict.Core/OpenIddictServices.cs b/src/OpenIddict.Core/OpenIddictServices.cs index 222212c2..929072d1 100644 --- a/src/OpenIddict.Core/OpenIddictServices.cs +++ b/src/OpenIddict.Core/OpenIddictServices.cs @@ -5,32 +5,66 @@ */ using System; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace OpenIddict { - public class OpenIddictServices { - public OpenIddictServices(IServiceCollection services) { + /// + /// Exposes the common services used by OpenIddict. + /// + public class OpenIddictServices where TUser : class where TApplication : class { + public OpenIddictServices([NotNull] IServiceProvider services) { Services = services; } /// - /// Gets or sets the type corresponding to the Application entity. + /// Gets the . /// - public Type ApplicationType { get; set; } + public virtual OpenIddictManager Applications { + get { return Services.GetRequiredService>(); } + } + + /// + /// Gets the optional . + /// + public virtual HttpContext Context { + get { return Services.GetService()?.HttpContext; } + } /// - /// Gets or sets the type corresponding to the Role entity. + /// Gets the . /// - public Type RoleType { get; set; } + public virtual ILogger Logger { + get { return Services.GetRequiredService>>(); } + } /// - /// Gets or sets the type corresponding to the User entity. + /// Gets the used to resolve services. /// - public Type UserType { get; set; } + public virtual IServiceProvider Services { get; } /// - /// Gets the services used by OpenIddict. + /// Gets the . /// - public IServiceCollection Services { get; } + public virtual SignInManager SignIn { + get { return Services.GetRequiredService>(); } + } + + /// + /// Gets the . + /// + public virtual IOpenIddictStore Store { + get { return Services.GetRequiredService>(); } + } + + /// + /// Gets the . + /// + public virtual UserManager Users { + get { return Services.GetRequiredService>(); } + } } } \ No newline at end of file diff --git a/src/OpenIddict.EF/OpenIddictExtensions.cs b/src/OpenIddict.EF/OpenIddictExtensions.cs index b436a8ad..43a4ff2b 100644 --- a/src/OpenIddict.EF/OpenIddictExtensions.cs +++ b/src/OpenIddict.EF/OpenIddictExtensions.cs @@ -13,32 +13,37 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using OpenIddict; +using OpenIddict.Models; namespace Microsoft.AspNetCore.Builder { public static class OpenIddictExtensions { - public static OpenIddictServices UseEntityFramework([NotNull] this OpenIddictServices services) { - if (services == null) { - throw new ArgumentNullException(nameof(services)); + public static OpenIddictConfiguration UseEntityFramework([NotNull] this OpenIddictConfiguration configuration) { + if (configuration == null) { + throw new ArgumentNullException(nameof(configuration)); } - services.Services.AddScoped( - typeof(IOpenIddictStore<,>).MakeGenericType(services.UserType, services.ApplicationType), - typeof(OpenIddictStore<,,,,>).MakeGenericType( - /* TUser: */ services.UserType, - /* TApplication: */ services.ApplicationType, - /* TRole: */ services.RoleType, - /* TContext: */ ResolveContextType(services), - /* TKey: */ ResolveKeyType(services))); + if (!IsSubclassOf(configuration.ApplicationType, typeof(Application<>))) { + throw new InvalidOperationException("The default store cannot be used with application " + + "entities that are not derived from Application."); + } + + configuration.Services.AddScoped( + typeof(IOpenIddictStore<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType), + typeof(OpenIddictStore<,,,>).MakeGenericType( + /* TUser: */ configuration.UserType, + /* TApplication: */ configuration.ApplicationType, + /* TContext: */ ResolveContextType(configuration), + /* TKey: */ ResolveKeyType(configuration))); - return services; + return configuration; } - private static Type ResolveContextType([NotNull] OpenIddictServices services) { - var service = (from registration in services.Services + private static Type ResolveContextType([NotNull] OpenIddictConfiguration configuration) { + var service = (from registration in configuration.Services where registration.ServiceType.IsConstructedGenericType let definition = registration.ServiceType.GetGenericTypeDefinition() where definition == typeof(IUserStore<>) - select registration.ImplementationType).SingleOrDefault(); + select registration.ImplementationType).FirstOrDefault(); if (service == null) { throw new InvalidOperationException( @@ -69,9 +74,9 @@ namespace Microsoft.AspNetCore.Builder { throw new InvalidOperationException("The type of the database context cannot be automatically inferred."); } - private static Type ResolveKeyType([NotNull] OpenIddictServices services) { + private static Type ResolveKeyType([NotNull] OpenIddictConfiguration configuration) { TypeInfo type; - for (type = services.UserType.GetTypeInfo(); type != null; type = type.BaseType?.GetTypeInfo()) { + for (type = configuration.UserType.GetTypeInfo(); type != null; type = type.BaseType?.GetTypeInfo()) { if (!type.IsGenericType) { continue; } @@ -90,7 +95,23 @@ namespace Microsoft.AspNetCore.Builder { throw new InvalidOperationException( "The type of the key identifier used by the user " + - $"entity '{services.UserType}' cannot be automatically inferred."); + $"entity '{configuration.UserType}' cannot be automatically inferred."); + } + + private static bool IsSubclassOf([NotNull] Type type, [NotNull] Type generic) { + while (type != null && type != typeof(object)) { + var current = type.GetTypeInfo().IsGenericType ? + type.GetGenericTypeDefinition() : + type; + + if (current == generic) { + return true; + } + + type = type.GetTypeInfo().BaseType; + } + + return false; } } } \ No newline at end of file diff --git a/src/OpenIddict.EF/OpenIddictStore.cs b/src/OpenIddict.EF/OpenIddictStore.cs index 23e838f0..78122392 100644 --- a/src/OpenIddict.EF/OpenIddictStore.cs +++ b/src/OpenIddict.EF/OpenIddictStore.cs @@ -6,16 +6,20 @@ using Microsoft.EntityFrameworkCore; using OpenIddict.Models; namespace OpenIddict { - public class OpenIddictStore : UserStore, IOpenIddictStore + public class OpenIddictStore : IOpenIddictStore where TUser : IdentityUser where TApplication : Application - where TRole : IdentityRole where TContext : DbContext where TKey : IEquatable { - public OpenIddictStore(TContext context) - : base(context) { + public OpenIddictStore(TContext context) { + Context = context; } + /// + /// Gets the database context associated with the current store. + /// + public virtual TContext Context { get; } + public DbSet Applications { get { return Context.Set(); } } diff --git a/src/OpenIddict.Mvc/OpenIddictController.cs b/src/OpenIddict.Mvc/OpenIddictController.cs index fc12cfb2..fa965bc8 100644 --- a/src/OpenIddict.Mvc/OpenIddictController.cs +++ b/src/OpenIddict.Mvc/OpenIddictController.cs @@ -18,22 +18,23 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace OpenIddict.Mvc { // Note: this controller is generic and doesn't need to be marked as internal to prevent MVC from discovering it. public class OpenIddictController : Controller where TUser : class where TApplication : class { public OpenIddictController( - [NotNull] OpenIddictManager manager, - [NotNull] OpenIddictOptions options) { - Manager = manager; - Options = options; + [NotNull] OpenIddictServices services, + [NotNull] IOptions options) { + Services = services; + Options = options.Value; } /// - /// Gets the OpenIddict manager used by the controller. + /// Gets the OpenIddict services used by the controller. /// - protected virtual OpenIddictManager Manager { get; } + protected virtual OpenIddictServices Services { get; } /// /// Gets the OpenIddict options used by the server. @@ -71,11 +72,10 @@ namespace OpenIddict.Mvc { }); } - // Note: ASOS automatically ensures that an application corresponds to the client_id specified - // in the authorization request by calling IOpenIdConnectServerProvider.ValidateAuthorizationRequest. - // In theory, this null check shouldn't be needed, but a race condition could occur if you - // manually removed the application details from the database after the initial check made by ASOS. - var application = await Manager.FindApplicationByIdAsync(request.ClientId); + // Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application + // corresponds to the client_id specified in the authorization request using + // IOpenIdConnectServerProvider.ValidateClientRedirectUri (see OpenIddictProvider.cs). + var application = await Services.Applications.FindApplicationByIdAsync(request.ClientId); if (application == null) { return View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidClient, @@ -83,7 +83,7 @@ namespace OpenIddict.Mvc { }); } - return View("Authorize", Tuple.Create(request, await Manager.GetDisplayNameAsync(application))); + return View("Authorize", Tuple.Create(request, await Services.Applications.GetDisplayNameAsync(application))); } [Authorize, HttpPost, ValidateAntiForgeryToken] @@ -102,7 +102,7 @@ namespace OpenIddict.Mvc { } // Retrieve the user data using the unique identifier. - var user = await Manager.GetUserAsync(User); + var user = await Services.Users.GetUserAsync(User); if (user == null) { return View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, @@ -112,17 +112,10 @@ namespace OpenIddict.Mvc { // Create a new ClaimsIdentity containing the claims that // will be used to create an id_token, a token or a code. - var identity = await Manager.CreateIdentityAsync(user, request.GetScopes()); + var identity = await Services.Applications.CreateIdentityAsync(user, request.GetScopes()); Debug.Assert(identity != null); - // Note: AspNet.Security.OpenIdConnect.Server automatically ensures an application - // corresponds to the client_id specified in the authorization request using - // IOpenIdConnectServerProvider.ValidateClientRedirectUri (see OpenIddictProvider.cs). - var application = await Manager.FindApplicationByIdAsync(request.ClientId); - - // In theory, this null check is thus not strictly necessary. That said, a race condition - // and a null reference exception could appear here if you manually removed the application - // details from the database after the initial check made by AspNet.Security.OpenIdConnect.Server. + var application = await Services.Applications.FindApplicationByIdAsync(request.ClientId); if (application == null) { return View("Error", new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidClient, @@ -136,7 +129,7 @@ namespace OpenIddict.Mvc { identity.Actor = new ClaimsIdentity(Options.AuthenticationScheme); identity.Actor.AddClaim(ClaimTypes.NameIdentifier, request.ClientId); - identity.Actor.AddClaim(ClaimTypes.Name, await Manager.GetDisplayNameAsync(application), + identity.Actor.AddClaim(ClaimTypes.Name, await Services.Applications.GetDisplayNameAsync(application), OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); diff --git a/src/OpenIddict.Mvc/OpenIddictExtensions.cs b/src/OpenIddict.Mvc/OpenIddictExtensions.cs index f1d18d41..a254d30a 100644 --- a/src/OpenIddict.Mvc/OpenIddictExtensions.cs +++ b/src/OpenIddict.Mvc/OpenIddictExtensions.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; using OpenIddict; using OpenIddict.Mvc; @@ -49,12 +50,12 @@ namespace Microsoft.AspNetCore.Builder { }); } }), services => { - var registration = app.ApplicationServices.GetRequiredService(); + var configuration = app.ApplicationServices.GetRequiredService(); services.AddMvc() // Register the OpenIddict controller. .AddControllersAsServices(new[] { - typeof(OpenIddictController<,>).MakeGenericType(registration.UserType, registration.ApplicationType) + typeof(OpenIddictController<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType) }) // Add an OpenIddict-specific convention to ensure that the generic @@ -69,30 +70,44 @@ namespace Microsoft.AspNetCore.Builder { baseNamespace: typeof(OpenIddictController<,>).Namespace)); }); + // Register the user manager in the isolated container. + services.AddScoped(typeof(OpenIddictManager<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType), provider => { + var accessor = provider.GetRequiredService(); + var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; + Debug.Assert(container != null); + + // Resolve the user manager from the parent container. + return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType)); + }); + + // Register the services context in the isolated container. + services.AddScoped(typeof(OpenIddictServices<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType), provider => { + var accessor = provider.GetRequiredService(); + var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; + Debug.Assert(container != null); + + // Resolve the services context from the parent container. + return container.GetRequiredService(typeof(OpenIddictServices<,>).MakeGenericType(configuration.UserType, configuration.ApplicationType)); + }); + // Register the sign-in manager in the isolated container. - services.AddScoped(typeof(SignInManager<>).MakeGenericType(registration.UserType), provider => { + services.AddScoped(typeof(SignInManager<>).MakeGenericType(configuration.UserType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null); // Resolve the sign-in manager from the parent container. - return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(registration.UserType)); + return container.GetRequiredService(typeof(SignInManager<>).MakeGenericType(configuration.UserType)); }); // Register the user manager in the isolated container. - services.AddScoped(typeof(OpenIddictManager<,>).MakeGenericType(registration.UserType, registration.ApplicationType), provider => { + services.AddScoped(typeof(UserManager<>).MakeGenericType(configuration.UserType), provider => { var accessor = provider.GetRequiredService(); var container = (IServiceProvider) accessor.HttpContext.Items[typeof(IServiceProvider)]; Debug.Assert(container != null); // Resolve the user manager from the parent container. - return container.GetRequiredService(typeof(OpenIddictManager<,>).MakeGenericType(registration.UserType, registration.ApplicationType)); - }); - - // Register the user manager in the isolated container. - services.AddScoped(typeof(UserManager<>).MakeGenericType(registration.UserType), provider => { - return provider.GetRequiredService(typeof(OpenIddictManager<,>) - .MakeGenericType(registration.UserType, registration.ApplicationType)); + return container.GetRequiredService(typeof(UserManager<>).MakeGenericType(configuration.UserType)); }); // Register the assembly provider in the isolated container. @@ -116,7 +131,7 @@ namespace Microsoft.AspNetCore.Builder { }); // Register the options in the isolated container. - services.AddScoped(provider => builder.Options); + services.AddSingleton(Options.Create(builder.Options)); })); }