/* * 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 System.Linq; using System.Text; using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using OpenIddict.Core; using OpenIddict.Server; namespace Microsoft.Extensions.DependencyInjection { public static class OpenIddictServerExtensions { /// /// Registers the OpenIddict token server services in the DI container. /// /// The services builder used by OpenIddict to register new services. /// This extension can be safely called multiple times. /// The . public static OpenIddictServerBuilder AddServer([NotNull] this OpenIddictBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.Services.AddAuthentication(); return new OpenIddictServerBuilder(builder.Services); } /// /// Registers the OpenIddict token server services in the DI container. /// /// The services builder used by OpenIddict to register new services. /// The configuration delegate used to configure the server services. /// This extension can be safely called multiple times. /// The . public static OpenIddictBuilder AddServer( [NotNull] this OpenIddictBuilder builder, [NotNull] Action configuration) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } configuration(builder.AddServer()); return builder; } /// /// Registers the OpenIddict server middleware in the ASP.NET Core pipeline. /// /// The application builder used to register middleware instances. /// The . public static IApplicationBuilder UseOpenIddictServer([NotNull] this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } var configuration = app.ApplicationServices.GetRequiredService>().Value; // Resolve the OpenIddict options from the DI container. var options = app.ApplicationServices.GetRequiredService>().Value; if (options.ApplicationType == null) { options.ApplicationType = configuration.DefaultApplicationType; } if (options.AuthorizationType == null) { options.AuthorizationType = configuration.DefaultAuthorizationType; } if (options.ScopeType == null) { options.ScopeType = configuration.DefaultScopeType; } if (options.TokenType == null) { options.TokenType = configuration.DefaultTokenType; } if (options.ApplicationType == null || options.AuthorizationType == null || options.ScopeType == null || options.TokenType == null) { throw new InvalidOperationException(new StringBuilder() .AppendLine("The entity types must be configured for the token server services to work correctly.") .Append("To configure the entities, use either 'services.AddOpenIddict().AddCore().UseDefaultModels()' ") .Append("or 'services.AddOpenIddict().AddCore().UseCustomModels()'.") .ToString()); } // When no authorization provider has been registered in the options, // create a new OpenIddictProvider instance using the specified entities. if (options.Provider == null) { options.Provider = (OpenIdConnectServerProvider) Activator.CreateInstance( typeof(OpenIddictServerProvider<,,,>).MakeGenericType( /* TApplication: */ options.ApplicationType, /* TAuthorization: */ options.AuthorizationType, /* TScope: */ options.ScopeType, /* TToken: */ options.TokenType)); } // When no distributed cache has been registered in the options, use the // global instance registered in the dependency injection container. if (options.Cache == null) { options.Cache = app.ApplicationServices.GetRequiredService(); } // If OpenIddict was configured to use reference tokens, replace the default access tokens/ // authorization codes/refresh tokens formats using a specific data protector to ensure // that encrypted tokens stored in the database cannot be treated as valid tokens if the // reference tokens option is later turned off by the developer. if (options.UseReferenceTokens) { // Note: a default data protection provider is always registered by // the OpenID Connect server handler when none is explicitly set but // this initializer is registered to be invoked before ASOS' initializer. // To ensure the provider property is never null, it's manually set here. if (options.DataProtectionProvider == null) { options.DataProtectionProvider = app.ApplicationServices.GetDataProtectionProvider(); } if (options.AccessTokenFormat == null) { var protector = options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerHandler), nameof(options.AccessTokenFormat), nameof(options.UseReferenceTokens), options.AuthenticationScheme); options.AccessTokenFormat = new TicketDataFormat(protector); } if (options.AuthorizationCodeFormat == null) { var protector = options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerHandler), nameof(options.AuthorizationCodeFormat), nameof(options.UseReferenceTokens), options.AuthenticationScheme); options.AuthorizationCodeFormat = new TicketDataFormat(protector); } if (options.RefreshTokenFormat == null) { var protector = options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerHandler), nameof(options.RefreshTokenFormat), nameof(options.UseReferenceTokens), options.AuthenticationScheme); options.RefreshTokenFormat = new TicketDataFormat(protector); } } // Ensure at least one flow has been enabled. if (options.GrantTypes.Count == 0) { throw new InvalidOperationException("At least one OAuth2/OpenID Connect flow must be enabled."); } // Ensure the authorization endpoint has been enabled when // the authorization code or implicit grants are supported. if (!options.AuthorizationEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit))) { throw new InvalidOperationException("The authorization endpoint must be enabled to use " + "the authorization code and implicit flows."); } // Ensure the token endpoint has been enabled when the authorization code, // client credentials, password or refresh token grants are supported. if (!options.TokenEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials) || options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Password) || options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken))) { throw new InvalidOperationException("The token endpoint must be enabled to use the authorization code, " + "client credentials, password and refresh token flows."); } if (options.RevocationEndpointPath.HasValue && options.DisableTokenRevocation) { throw new InvalidOperationException("The revocation endpoint cannot be enabled when token revocation is disabled."); } if (options.UseReferenceTokens && options.DisableTokenRevocation) { throw new InvalidOperationException( "Reference tokens cannot be used when disabling token revocation."); } if (options.UseReferenceTokens && options.AccessTokenHandler != null) { throw new InvalidOperationException( "Reference tokens cannot be used when configuring JWT as the access token format."); } if (options.UseSlidingExpiration && options.DisableTokenRevocation && !options.UseRollingTokens) { throw new InvalidOperationException("Sliding expiration must be disabled when turning off " + "token revocation if rolling tokens are not used."); } // Ensure at least one asymmetric signing certificate/key was registered if the implicit flow was enabled. if (!options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey) && options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit)) { throw new InvalidOperationException("At least one asymmetric signing key must be registered when enabling the implicit flow. " + "Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' " + "or call 'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key."); } // Automatically add the offline_access scope if the refresh token grant has been enabled. if (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { options.Scopes.Add(OpenIdConnectConstants.Scopes.OfflineAccess); } return app.UseOpenIdConnectServer(options); } } }