/// <summary> /// Authorization Server middleware component which is added to an OWIN pipeline. This constructor is not /// called by application code directly, instead it is added by calling the the IAppBuilder UseOpenIdConnectServer /// extension method. /// </summary> public OpenIdConnectServerMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectServerOptions options) : base(next, options) { if (Options.HtmlEncoder == null) { throw new ArgumentException("The HTML encoder registered in the options " + "cannot be null.", nameof(options)); } if (Options.Provider == null) { throw new ArgumentException("The authorization provider registered in " + "the options cannot be null.", nameof(options)); } if (Options.SystemClock == null) { throw new ArgumentException("The system clock registered in the options " + "cannot be null.", nameof(options)); } if (Options.Issuer != null) { if (!Options.Issuer.IsAbsoluteUri) { throw new ArgumentException("The issuer registered in the options must be " + "a valid absolute URI.", nameof(options)); } // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery if (!string.IsNullOrEmpty(Options.Issuer.Query) || !string.IsNullOrEmpty(Options.Issuer.Fragment)) { throw new ArgumentException("The issuer registered in the options must contain no query " + "and no fragment parts.", nameof(options)); } // Note: while the issuer parameter should be a HTTPS URI, making HTTPS mandatory // in Owin.Security.OpenIdConnect.Server would prevent the end developer from // running the different samples in test environments, where HTTPS is often disabled. // To mitigate this issue, AllowInsecureHttp can be set to true to bypass the HTTPS check. // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery if (!Options.AllowInsecureHttp && string.Equals(Options.Issuer.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("The issuer registered in the options must be a HTTPS URI when " + "AllowInsecureHttp is not set to true.", nameof(options)); } } if (Options.AccessTokenHandler != null && !(Options.AccessTokenHandler is ISecurityTokenValidator)) { throw new ArgumentException("The access token handler must implement 'ISecurityTokenValidator'.", nameof(options)); } if (Options.Logger == null) { Options.Logger = new LoggerFactory().CreateLogger <OpenIdConnectServerMiddleware>(); } if (Options.DataProtectionProvider == null) { // Try to use the application name provided by // the OWIN host as the application discriminator. var discriminator = new AppProperties(app.Properties).AppName; // When an application discriminator cannot be resolved from // the OWIN host properties, generate a temporary identifier. if (string.IsNullOrEmpty(discriminator)) { discriminator = Guid.NewGuid().ToString(); } Options.DataProtectionProvider = DataProtectionProvider.Create(discriminator); } if (Options.AccessTokenFormat == null) { var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Access_Token", "v1"); Options.AccessTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)); } if (Options.AuthorizationCodeFormat == null) { var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Authorization_Code", "v1"); Options.AuthorizationCodeFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)); } if (Options.RefreshTokenFormat == null) { var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Refresh_Token", "v1"); Options.RefreshTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)); } var environment = new AppProperties(app.Properties).Get <string>("host.AppMode"); if (Options.AllowInsecureHttp && !string.Equals(environment, "Development", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogWarning("Disabling the transport security requirement is not recommended on production. " + "Consider setting 'OpenIdConnectServerOptions.AllowInsecureHttp' to 'false' " + "to prevent the OpenID Connect server middleware from serving non-HTTPS requests."); } // If no key has been explicitly added, use the fallback mode. if (Options.EncryptingCredentials.Count == 0 || Options.SigningCredentials.Count == 0) { // When detecting a non-development environment, log a warning to inform the developer // that registering a X.509 certificate is recommended when going live. if (!string.Equals(environment, "Development", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogWarning("No explicit signing credentials have been registered. " + "Using a X.509 certificate stored in the machine store " + "is recommended for production environments."); } var directory = OpenIdConnectServerHelpers.GetDefaultKeyStorageDirectory(); if (directory == null) { Options.Logger.LogError("No suitable directory can be found to store the dynamically-generated RSA keys."); return; } // Get a data protector from the services provider. var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Keys", "v1"); // Note: all the exceptions thrown when enumerating // existing keys or generating new ones must be caught. try { foreach (var key in OpenIdConnectServerHelpers.GetKeys(directory)) { // Extract the key material using the data protector. // Ignore the key if the decryption process failed. string usage; var parameters = OpenIdConnectServerHelpers.DecryptKey(protector, key.Value, out usage); if (parameters == null) { Options.Logger.LogDebug("An invalid/incompatible key was ignored: {Key}.", key.Key); continue; } if (string.Equals(usage, "Encryption", StringComparison.OrdinalIgnoreCase)) { var algorithm = RSA.Create(); algorithm.ImportParameters(parameters.Value); // Add the key to the encryption credentials list. Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm)); Options.Logger.LogInformation("An existing key was automatically added to the " + "encryption credentials list: {Key}.", key.Key); } else if (string.Equals(usage, "Signing", StringComparison.OrdinalIgnoreCase)) { var algorithm = RSA.Create(); algorithm.ImportParameters(parameters.Value); // Add the key to the signing credentials list. Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm)); Options.Logger.LogInformation("An existing key was automatically added to the " + "signing credentials list: {Key}.", key.Key); } } } catch (Exception exception) { Options.Logger.LogDebug("An error ocurred when retrieving the keys stored " + "on the disk: {Exception}.", exception.ToString()); } // Note: all the exceptions thrown when enumerating // existing keys or generating new ones must be caught. try { // If no encryption key has been found, generate and persist a new RSA key. if (Options.EncryptingCredentials.Count == 0) { // Generate a new RSA key and export its public/private parameters. var algorithm = OpenIdConnectServerHelpers.GenerateKey(size: 2048); var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true); // Add the encryption key to the credentials list before trying to persist it on the disk. Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm)); try { // Encrypt the key using the data protector and try to persist it on the disk. var key = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Encryption"); var path = OpenIdConnectServerHelpers.PersistKey(directory, key); Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " + "encryption credentials list and persisted on the disk: {Path}.", path); } catch (Exception exception) { Options.Logger.LogWarning("An error ocurred when persisting a new key on the disk: {Exception}. " + "The key will be kept in memory and will be lost when the " + "application shuts down or restarts.", exception.ToString()); } } // If no signing key has been found, generate and persist a new RSA key. if (Options.SigningCredentials.Count == 0) { // Generate a new RSA key and export its public/private parameters. var algorithm = OpenIdConnectServerHelpers.GenerateKey(size: 2048); var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true); // Add the signing key to the credentials list before trying to persist it on the disk. Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm)); try { // Encrypt the key using the data protector and try to persist it on the disk. var key = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Signing"); var path = OpenIdConnectServerHelpers.PersistKey(directory, key); Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " + "signing credentials list and persisted on the disk: {Path}.", path); } catch (Exception exception) { Options.Logger.LogWarning("An error ocurred when persisting a new key on the disk: {Exception}. " + "The key will be kept in memory and will be lost when the " + "application shuts down or restarts.", exception.ToString()); } } } catch (Exception exception) { Options.Logger.LogDebug("An error ocurred when generating a new key: {Exception}.", exception.ToString()); } } if (Options.SigningCredentials.Count == 0) { throw new ArgumentException("At least one signing key must be registered.", nameof(options)); } }
/// <summary> /// Authorization Server middleware component which is added to an OWIN pipeline. This constructor is not /// called by application code directly, instead it is added by calling the the IAppBuilder UseOpenIdConnectServer /// extension method. /// </summary> public OpenIdConnectServerMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectServerOptions options) : base(next, options) { if (Options.HtmlEncoder == null) { throw new ArgumentException("The HTML encoder registered in the options " + "cannot be null.", nameof(options)); } if (Options.Provider == null) { throw new ArgumentException("The authorization provider registered in " + "the options cannot be null.", nameof(options)); } if (Options.RandomNumberGenerator == null) { throw new ArgumentException("The random number generator registered in " + "the options cannot be null.", nameof(options)); } if (Options.SystemClock == null) { throw new ArgumentException("The system clock registered in the options " + "cannot be null.", nameof(options)); } if (Options.Issuer != null) { if (!Options.Issuer.IsAbsoluteUri) { throw new ArgumentException("The issuer registered in the options must be " + "a valid absolute URI.", nameof(options)); } // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery if (!string.IsNullOrEmpty(Options.Issuer.Query) || !string.IsNullOrEmpty(Options.Issuer.Fragment)) { throw new ArgumentException("The issuer registered in the options must contain no query " + "and no fragment parts.", nameof(options)); } // Note: while the issuer parameter should be a HTTPS URI, making HTTPS mandatory // in Owin.Security.OpenIdConnect.Server would prevent the end developer from // running the different samples in test environments, where HTTPS is often disabled. // To mitigate this issue, AllowInsecureHttp can be set to true to bypass the HTTPS check. // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery if (!Options.AllowInsecureHttp && string.Equals(Options.Issuer.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("The issuer registered in the options must be a HTTPS URI when " + "AllowInsecureHttp is not set to true.", nameof(options)); } } if (Options.Logger == null) { Options.Logger = new LoggerFactory().CreateLogger <OpenIdConnectServerMiddleware>(); } if (Options.DataProtectionProvider == null) { // Create a new DI container and register // the data protection services. var services = new ServiceCollection(); services.AddDataProtection(configuration => { // Try to use the application name provided by // the OWIN host as the application discriminator. var discriminator = new AppProperties(app.Properties).AppName; // When an application discriminator cannot be resolved from // the OWIN host properties, generate a temporary identifier. if (string.IsNullOrEmpty(discriminator)) { discriminator = Guid.NewGuid().ToString(); } configuration.ApplicationDiscriminator = discriminator; }); var container = services.BuildServiceProvider(); // Resolve a data protection provider from the services container. Options.DataProtectionProvider = container.GetRequiredService <IDataProtectionProvider>(); } if (Options.AccessTokenFormat == null) { var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Access_Token", "v1"); Options.AccessTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)); } if (Options.AuthorizationCodeFormat == null) { var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Authorization_Code", "v1"); Options.AuthorizationCodeFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)); } if (Options.RefreshTokenFormat == null) { var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Refresh_Token", "v1"); Options.RefreshTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)); } // If no key has been explicitly added, use the fallback mode. if (Options.EncryptingCredentials.Count == 0 || Options.SigningCredentials.Count == 0) { var directory = OpenIdConnectServerHelpers.GetDefaultKeyStorageDirectory(); Debug.Assert(directory != null); // Get a data protector from the services provider. var protector = Options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerMiddleware), Options.AuthenticationType, "Keys", "v1"); foreach (var file in directory.EnumerateFiles("*.key")) { using (var buffer = new MemoryStream()) using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { // Copy the key content to the buffer. stream.CopyTo(buffer); // Extract the key material using the data protector. // Ignore the key if the decryption process failed. string usage; var parameters = OpenIdConnectServerHelpers.DecryptKey(protector, buffer.ToArray(), out usage); if (parameters == null) { Options.Logger.LogDebug("An invalid/incompatible key was ignored: {Key}.", file.FullName); continue; } if (string.Equals(usage, "Encryption", StringComparison.OrdinalIgnoreCase)) { // Only add the encryption key if none has been explictly added. if (Options.EncryptingCredentials.Count != 0) { continue; } var algorithm = RSA.Create(); algorithm.ImportParameters(parameters.Value); // Add the key to the encryption credentials list. Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm)); Options.Logger.LogInformation("An existing key was automatically added to the " + "encryption credentials list: {Key}.", file.FullName); } else if (string.Equals(usage, "Signing", StringComparison.OrdinalIgnoreCase)) { // Only add the signing key if none has been explictly added. if (Options.SigningCredentials.Count != 0) { continue; } var algorithm = RSA.Create(); algorithm.ImportParameters(parameters.Value); // Add the key to the signing credentials list. Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm)); Options.Logger.LogInformation("An existing key was automatically added to the " + "signing credentials list: {Key}.", file.FullName); } } } // If no encryption key has been found, generate and persist a new RSA key. if (Options.EncryptingCredentials.Count == 0) { // Generate a new RSA key and export its public/private parameters. var algorithm = OpenIdConnectServerHelpers.GenerateKey(size: 2048); var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true); // Generate a new file name for the key and determine its absolute path. var path = Path.Combine(directory.FullName, Guid.NewGuid().ToString() + ".key"); using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write)) { // Encrypt the key using the data protector. var bytes = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Encryption"); // Write the encrypted key to the file stream. stream.Write(bytes, 0, bytes.Length); } Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm)); Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " + "encryption credentials list and persisted on the disk: {Path}.", path); } // If no signing key has been found, generate and persist a new RSA key. if (Options.SigningCredentials.Count == 0) { // Generate a new RSA key and export its public/private parameters. var algorithm = OpenIdConnectServerHelpers.GenerateKey(size: 2048); var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true); // Generate a new file name for the key and determine its absolute path. var path = Path.Combine(directory.FullName, Guid.NewGuid().ToString() + ".key"); using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write)) { // Encrypt the key using the data protector. var bytes = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Signing"); // Write the encrypted key to the file stream. stream.Write(bytes, 0, bytes.Length); } Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm)); Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " + "signing credentials list and persisted on the disk: {Path}.", path); } } }