/* * 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.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.IdentityModel.Tokens; using OpenIddict.Server; namespace Microsoft.Extensions.DependencyInjection; /// /// Exposes the necessary methods required to configure the OpenIddict server services. /// public sealed class OpenIddictServerBuilder { /// /// Initializes a new instance of . /// /// The services collection. public OpenIddictServerBuilder(IServiceCollection services) => Services = services ?? throw new ArgumentNullException(nameof(services)); /// /// Gets the services collection. /// [EditorBrowsable(EditorBrowsableState.Never)] public IServiceCollection Services { get; } /// /// Registers an event handler using the specified configuration delegate. /// /// The event context type. /// The configuration delegate. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder AddEventHandler( Action> configuration) where TContext : BaseContext { ArgumentNullException.ThrowIfNull(configuration); // Note: handlers registered using this API are assumed to be custom handlers by default. var builder = OpenIddictServerHandlerDescriptor.CreateBuilder() .SetType(OpenIddictServerHandlerType.Custom); configuration(builder); return AddEventHandler(builder.Build()); } /// /// Registers an event handler using the specified descriptor. /// /// The handler descriptor. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder AddEventHandler(OpenIddictServerHandlerDescriptor descriptor) { ArgumentNullException.ThrowIfNull(descriptor); // Register the handler in the services collection. Services.Add(descriptor.ServiceDescriptor); return Configure(options => options.Handlers.Add(descriptor)); } /// /// Removes the event handler that matches the specified descriptor. /// /// The descriptor corresponding to the handler to remove. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder RemoveEventHandler(OpenIddictServerHandlerDescriptor descriptor) { ArgumentNullException.ThrowIfNull(descriptor); Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType); Services.PostConfigure(options => { for (var index = options.Handlers.Count - 1; index >= 0; index--) { if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) { options.Handlers.RemoveAt(index); } } }); return this; } /// /// Amends the default OpenIddict server configuration. /// /// The delegate used to configure the OpenIddict options. /// This extension can be safely called multiple times. /// The instance. public OpenIddictServerBuilder Configure(Action configuration) { ArgumentNullException.ThrowIfNull(configuration); Services.Configure(configuration); return this; } /// /// Makes client identification optional so that token, introspection and revocation /// requests that don't specify a client_id are not automatically rejected. /// Enabling this option is NOT recommended. /// /// The instance. public OpenIddictServerBuilder AcceptAnonymousClients() => Configure(options => options.AcceptAnonymousClients = true); /// /// Registers encryption credentials. /// /// The encrypting credentials. /// The instance. public OpenIddictServerBuilder AddEncryptionCredentials(EncryptingCredentials credentials) { ArgumentNullException.ThrowIfNull(credentials); return Configure(options => options.EncryptionCredentials.Add(credentials)); } /// /// Registers an encryption key. /// /// The security key. /// The instance. public OpenIddictServerBuilder AddEncryptionKey(SecurityKey key) { ArgumentNullException.ThrowIfNull(key); // If the encryption key is an asymmetric security key, ensure it has a private key. if (key is AsymmetricSecurityKey { PrivateKeyStatus: PrivateKeyStatus.DoesNotExist }) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0055)); } if (key.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)) { if (key.KeySize != 256) { throw new InvalidOperationException(SR.FormatID0283(256, key.KeySize)); } return AddEncryptionCredentials(new EncryptingCredentials(key, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)); } if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaOAEP)) { return AddEncryptionCredentials(new EncryptingCredentials(key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); } throw new InvalidOperationException(SR.GetResourceString(SR.ID0056)); } /// /// Registers multiple encryption keys. /// /// The security keys. /// The instance. public OpenIddictServerBuilder AddEncryptionKeys(IEnumerable keys) { ArgumentNullException.ThrowIfNull(keys); return keys.Aggregate(this, static (builder, key) => builder.AddEncryptionKey(key)); } /// /// Registers (and generates if necessary) a user-specific development encryption certificate. /// /// The instance. public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate() => AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Server Encryption Certificate")); /// /// Registers (and generates if necessary) a user-specific development encryption certificate. /// /// The subject name associated with the certificate. /// The instance. public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) { ArgumentNullException.ThrowIfNull(subject); Services.AddOptions().Configure((options, provider) => { // Important: the time provider might not be set yet when this configuration delegate is called. // In that case, resolve the provider from the service provider or use the default time provider. var now = (options.TimeProvider ?? provider.GetService() ?? TimeProvider.System).GetUtcNow(); using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); // Try to retrieve the existing development certificates from the specified store. // If no valid existing certificate was found, create a new encryption certificate. var certificates = store.Certificates .Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) .Cast() .ToList(); if (!certificates.Exists(certificate => certificate.NotBefore < now.LocalDateTime && certificate.NotAfter > now.LocalDateTime)) { #if SUPPORTS_CERTIFICATE_GENERATION using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 4096); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); var certificate = request.CreateSelfSigned(now, now.AddYears(2)); // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. if (OperatingSystem.IsWindows()) { certificate.FriendlyName = "OpenIddict Server Development Encryption Certificate"; } // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key. // To work around this issue, the certificate payload is manually exported and imported back // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag. var data = certificate.Export(X509ContentType.Pfx, string.Empty); try { var flags = X509KeyStorageFlags.PersistKeySet; // Note: macOS requires marking the certificate private key as exportable. // If this flag is not set, a CryptographicException is thrown at runtime. if (OperatingSystem.IsMacOS()) { flags |= X509KeyStorageFlags.Exportable; } #if SUPPORTS_CERTIFICATE_LOADER certificate = X509CertificateLoader.LoadPkcs12(data, string.Empty, flags); #else certificate = new X509Certificate2(data, string.Empty, flags); #endif certificates.Insert(0, certificate); } finally { Array.Clear(data, 0, data.Length); } store.Add(certificate); #else throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); #endif } options.EncryptionCredentials.AddRange( from certificate in certificates let key = new X509SecurityKey(certificate) select new EncryptingCredentials(key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); }); return this; } /// /// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically /// discarded when the application shuts down and payloads encrypted using this key are /// automatically invalidated. This method should only be used during development. /// On production, using a X.509 certificate stored in the machine store is recommended. /// /// The instance. public OpenIddictServerBuilder AddEphemeralEncryptionKey() => AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP); /// /// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically /// discarded when the application shuts down and payloads encrypted using this key are /// automatically invalidated. This method should only be used during development. /// On production, using a X.509 certificate stored in the machine store is recommended. /// /// The algorithm associated with the encryption key. /// The instance. public OpenIddictServerBuilder AddEphemeralEncryptionKey(string algorithm) { ArgumentException.ThrowIfNullOrEmpty(algorithm); return algorithm switch { SecurityAlgorithms.Aes256KW => AddEncryptionCredentials(new EncryptingCredentials( new SymmetricSecurityKey(OpenIddictHelpers.CreateRandomArray(size: 256)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), SecurityAlgorithms.RsaOAEP or SecurityAlgorithms.RsaOaepKeyWrap => AddEncryptionCredentials(new EncryptingCredentials( new RsaSecurityKey(OpenIddictHelpers.CreateRsaKey(size: 4096)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) }; } /// /// Registers an encryption certificate. /// /// The encryption certificate. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate(X509Certificate2 certificate) { ArgumentNullException.ThrowIfNull(certificate); // If the certificate is a X.509v3 certificate that specifies at least one // key usage, ensure that the certificate key can be used for key encryption. if (certificate.Version is >= 3) { var extensions = certificate.Extensions.OfType().ToList(); if (extensions.Count is not 0 && !extensions.Exists(static extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment))) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0060)); } } if (!certificate.HasPrivateKey) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); } return AddEncryptionKey(new X509SecurityKey(certificate)); } /// /// Registers an encryption certificate retrieved from an embedded resource. /// /// The assembly containing the certificate. /// The name of the embedded resource. /// The password used to open the certificate. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate(Assembly assembly, string resource, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS // Note: ephemeral key sets are currently not supported on macOS. => AddEncryptionCertificate(assembly, resource, password, OperatingSystem.IsMacOS() ? X509KeyStorageFlags.MachineKeySet : X509KeyStorageFlags.EphemeralKeySet); #else => AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); #endif /// /// Registers an encryption certificate retrieved from an embedded resource. /// /// The assembly containing the certificate. /// The name of the embedded resource. /// The password used to open the certificate. /// An enumeration of flags indicating how and where to store the private key of the certificate. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate( Assembly assembly, string resource, string? password, X509KeyStorageFlags flags) { ArgumentNullException.ThrowIfNull(assembly); ArgumentException.ThrowIfNullOrEmpty(resource); using var stream = assembly.GetManifestResourceStream(resource) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); return AddEncryptionCertificate(stream, password, flags); } /// /// Registers an encryption certificate extracted from a stream. /// /// The stream containing the certificate. /// The password used to open the certificate. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS // Note: ephemeral key sets are currently not supported on macOS. => AddEncryptionCertificate(stream, password, OperatingSystem.IsMacOS() ? X509KeyStorageFlags.MachineKeySet : X509KeyStorageFlags.EphemeralKeySet); #else => AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); #endif /// /// Registers an encryption certificate extracted from a stream. /// /// The stream containing the certificate. /// The password used to open the certificate. /// An enumeration of flags indicating how and where to store the private key of the certificate. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags) { ArgumentNullException.ThrowIfNull(stream); using var buffer = new MemoryStream(); stream.CopyTo(buffer); #if SUPPORTS_CERTIFICATE_LOADER var certificate = X509Certificate2.GetCertContentType(buffer.ToArray()) switch { X509ContentType.Pkcs12 => X509CertificateLoader.LoadPkcs12(buffer.ToArray(), password, flags), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0454)) }; #else var certificate = new X509Certificate2(buffer.ToArray(), password, flags); #endif return AddEncryptionCertificate(certificate); } /// /// Registers an encryption certificate retrieved from the X.509 user or machine store. /// /// The thumbprint of the certificate used to identify it in the X.509 store. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate(string thumbprint) { ArgumentException.ThrowIfNullOrEmpty(thumbprint); return AddEncryptionCertificate( GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0066))); static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) { using var store = new X509Store(StoreName.My, location); store.Open(OpenFlags.ReadOnly); return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .Cast() .SingleOrDefault(); } } /// /// Registers an encryption certificate retrieved from the specified X.509 store. /// /// The thumbprint of the certificate used to identify it in the X.509 store. /// The name of the X.509 store. /// The location of the X.509 store. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificate(string thumbprint, StoreName name, StoreLocation location) { ArgumentException.ThrowIfNullOrEmpty(thumbprint); using var store = new X509Store(name, location); store.Open(OpenFlags.ReadOnly); return AddEncryptionCertificate( store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .Cast() .SingleOrDefault() ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0066))); } /// /// Registers multiple encryption certificates. /// /// The encryption certificates. /// The instance. public OpenIddictServerBuilder AddEncryptionCertificates(IEnumerable certificates) { ArgumentNullException.ThrowIfNull(certificates); return certificates.Aggregate(this, static (builder, certificate) => builder.AddEncryptionCertificate(certificate)); } /// /// Registers signing credentials. /// /// The signing credentials. /// The instance. public OpenIddictServerBuilder AddSigningCredentials(SigningCredentials credentials) { ArgumentNullException.ThrowIfNull(credentials); return Configure(options => options.SigningCredentials.Add(credentials)); } /// /// Registers a signing key. /// /// The security key. /// The instance. public OpenIddictServerBuilder AddSigningKey(SecurityKey key) { ArgumentNullException.ThrowIfNull(key); // If the signing key is an asymmetric security key, ensure it has a private key. if (key is AsymmetricSecurityKey { PrivateKeyStatus: PrivateKeyStatus.DoesNotExist }) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0067)); } if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256)) { return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.RsaSha256)); } if (key.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256)) { return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)); } #if SUPPORTS_ECDSA // Note: ECDSA algorithms are bound to specific curves and must be treated separately. if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256)) { return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256)); } if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384)) { return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha384)); } if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) { return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha512)); } #else if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) || key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) || key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) { throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)); } #endif throw new InvalidOperationException(SR.GetResourceString(SR.ID0068)); } /// /// Registers multiple signing keys. /// /// The signing keys. /// The instance. public OpenIddictServerBuilder AddSigningKeys(IEnumerable keys) { ArgumentNullException.ThrowIfNull(keys); return keys.Aggregate(this, static (builder, key) => builder.AddSigningKey(key)); } /// /// Registers (and generates if necessary) a user-specific development signing certificate. /// /// The instance. public OpenIddictServerBuilder AddDevelopmentSigningCertificate() => AddDevelopmentSigningCertificate(new X500DistinguishedName("CN=OpenIddict Server Signing Certificate")); /// /// Registers (and generates if necessary) a user-specific development signing certificate. /// /// The subject name associated with the certificate. /// The instance. public OpenIddictServerBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) { ArgumentNullException.ThrowIfNull(subject); Services.AddOptions().Configure((options, provider) => { // Important: the time provider might not be set yet when this configuration delegate is called. // In that case, resolve the provider from the service provider or use the default time provider. var now = (options.TimeProvider ?? provider.GetService() ?? TimeProvider.System).GetUtcNow(); using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); // Try to retrieve the existing development certificates from the specified store. // If no valid existing certificate was found, create a new signing certificate. var certificates = store.Certificates .Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) .Cast() .ToList(); if (!certificates.Exists(certificate => certificate.NotBefore < now.LocalDateTime && certificate.NotAfter > now.LocalDateTime)) { #if SUPPORTS_CERTIFICATE_GENERATION using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 4096); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); var certificate = request.CreateSelfSigned(now, now.AddYears(2)); // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. if (OperatingSystem.IsWindows()) { certificate.FriendlyName = "OpenIddict Server Development Signing Certificate"; } // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key. // To work around this issue, the certificate payload is manually exported and imported back // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag. var data = certificate.Export(X509ContentType.Pfx, string.Empty); try { var flags = X509KeyStorageFlags.PersistKeySet; // Note: macOS requires marking the certificate private key as exportable. // If this flag is not set, a CryptographicException is thrown at runtime. if (OperatingSystem.IsMacOS()) { flags |= X509KeyStorageFlags.Exportable; } #if SUPPORTS_CERTIFICATE_LOADER certificate = X509CertificateLoader.LoadPkcs12(data, string.Empty, flags); #else certificate = new X509Certificate2(data, string.Empty, flags); #endif certificates.Insert(0, certificate); } finally { Array.Clear(data, 0, data.Length); } store.Add(certificate); #else throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); #endif } options.SigningCredentials.AddRange( from certificate in certificates let key = new X509SecurityKey(certificate) select new SigningCredentials(key, SecurityAlgorithms.RsaSha256)); }); return this; } /// /// Registers a new ephemeral signing key. Ephemeral signing keys are automatically /// discarded when the application shuts down and payloads signed using this key are /// automatically invalidated. This method should only be used during development. /// On production, using a X.509 certificate stored in the machine store is recommended. /// /// The instance. public OpenIddictServerBuilder AddEphemeralSigningKey() => AddEphemeralSigningKey(SecurityAlgorithms.RsaSha256); /// /// Registers a new ephemeral signing key. Ephemeral signing keys are automatically /// discarded when the application shuts down and payloads signed using this key are /// automatically invalidated. This method should only be used during development. /// On production, using a X.509 certificate stored in the machine store is recommended. /// /// The algorithm associated with the signing key. /// The instance. public OpenIddictServerBuilder AddEphemeralSigningKey(string algorithm) { ArgumentException.ThrowIfNullOrEmpty(algorithm); return algorithm switch { SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha256Signature or SecurityAlgorithms.RsaSha384Signature or SecurityAlgorithms.RsaSha512Signature or SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha256Signature or SecurityAlgorithms.RsaSsaPssSha384Signature or SecurityAlgorithms.RsaSsaPssSha512Signature => AddSigningCredentials(new SigningCredentials(new RsaSecurityKey( OpenIddictHelpers.CreateRsaKey(size: 4096)), algorithm)), #if SUPPORTS_ECDSA SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP256)), algorithm)), SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP384)), algorithm)), SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP521)), algorithm)), #else SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha256Signature or SecurityAlgorithms.EcdsaSha384Signature or SecurityAlgorithms.EcdsaSha512Signature => throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)), #endif _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) }; } /// /// Registers a signing certificate. /// /// The signing certificate. /// The instance. public OpenIddictServerBuilder AddSigningCertificate(X509Certificate2 certificate) { ArgumentNullException.ThrowIfNull(certificate); // If the certificate is a X.509v3 certificate that specifies at least // one key usage, ensure that the certificate key can be used for signing. if (certificate.Version is >= 3) { var extensions = certificate.Extensions.OfType().ToList(); if (extensions.Count is not 0 && !extensions.Exists(static extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.DigitalSignature))) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0070)); } } if (!certificate.HasPrivateKey) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); } return AddSigningKey(new X509SecurityKey(certificate)); } /// /// Registers a signing certificate retrieved from an embedded resource. /// /// The assembly containing the certificate. /// The name of the embedded resource. /// The password used to open the certificate. /// The instance. public OpenIddictServerBuilder AddSigningCertificate(Assembly assembly, string resource, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS // Note: ephemeral key sets are currently not supported on macOS. => AddSigningCertificate(assembly, resource, password, OperatingSystem.IsMacOS() ? X509KeyStorageFlags.MachineKeySet : X509KeyStorageFlags.EphemeralKeySet); #else => AddSigningCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); #endif /// /// Registers a signing certificate retrieved from an embedded resource. /// /// The assembly containing the certificate. /// The name of the embedded resource. /// The password used to open the certificate. /// An enumeration of flags indicating how and where to store the private key of the certificate. /// The instance. public OpenIddictServerBuilder AddSigningCertificate( Assembly assembly, string resource, string? password, X509KeyStorageFlags flags) { ArgumentNullException.ThrowIfNull(assembly); ArgumentException.ThrowIfNullOrEmpty(resource); using var stream = assembly.GetManifestResourceStream(resource) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); return AddSigningCertificate(stream, password, flags); } /// /// Registers a signing certificate extracted from a stream. /// /// The stream containing the certificate. /// The password used to open the certificate. /// The instance. public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS // Note: ephemeral key sets are currently not supported on macOS. => AddSigningCertificate(stream, password, OperatingSystem.IsMacOS() ? X509KeyStorageFlags.MachineKeySet : X509KeyStorageFlags.EphemeralKeySet); #else => AddSigningCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); #endif /// /// Registers a signing certificate extracted from a stream. /// /// The stream containing the certificate. /// The password used to open the certificate. /// An enumeration of flags indicating how and where to store the private key of the certificate. /// The instance. public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags) { ArgumentNullException.ThrowIfNull(stream); using var buffer = new MemoryStream(); stream.CopyTo(buffer); #if SUPPORTS_CERTIFICATE_LOADER var certificate = X509Certificate2.GetCertContentType(buffer.ToArray()) switch { X509ContentType.Pkcs12 => X509CertificateLoader.LoadPkcs12(buffer.ToArray(), password, flags), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0454)) }; #else var certificate = new X509Certificate2(buffer.ToArray(), password, flags); #endif return AddSigningCertificate(certificate); } /// /// Registers a signing certificate retrieved from the X.509 user or machine store. /// /// The thumbprint of the certificate used to identify it in the X.509 store. /// The instance. public OpenIddictServerBuilder AddSigningCertificate(string thumbprint) { ArgumentException.ThrowIfNullOrEmpty(thumbprint); return AddSigningCertificate( GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0066))); static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) { using var store = new X509Store(StoreName.My, location); store.Open(OpenFlags.ReadOnly); return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .Cast() .SingleOrDefault(); } } /// /// Registers a signing certificate retrieved from the specified X.509 store. /// /// The thumbprint of the certificate used to identify it in the X.509 store. /// The name of the X.509 store. /// The location of the X.509 store. /// The instance. public OpenIddictServerBuilder AddSigningCertificate(string thumbprint, StoreName name, StoreLocation location) { ArgumentException.ThrowIfNullOrEmpty(thumbprint); using var store = new X509Store(name, location); store.Open(OpenFlags.ReadOnly); return AddSigningCertificate( store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .Cast() .SingleOrDefault() ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0066))); } /// /// Registers multiple signing certificates. /// /// The signing certificates. /// The instance. public OpenIddictServerBuilder AddSigningCertificates(IEnumerable certificates) { ArgumentNullException.ThrowIfNull(certificates); return certificates.Aggregate(this, static (builder, certificate) => builder.AddSigningCertificate(certificate)); } /// /// Enables authorization code flow support. For more information /// about this specific OAuth 2.0/OpenID Connect flow, visit /// https://tools.ietf.org/html/rfc6749#section-4.1 and /// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. /// /// The instance. public OpenIddictServerBuilder AllowAuthorizationCodeFlow() => Configure(options => { options.GrantTypes.Add(GrantTypes.AuthorizationCode); options.ResponseTypes.Add(ResponseTypes.Code); }); /// /// Enables client credentials flow support. For more information about this /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. /// /// The instance. public OpenIddictServerBuilder AllowClientCredentialsFlow() => Configure(options => options.GrantTypes.Add(GrantTypes.ClientCredentials)); /// /// Enables custom grant type support. /// /// The grant type associated with the flow. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder AllowCustomFlow(string type) { ArgumentException.ThrowIfNullOrEmpty(type); return Configure(options => options.GrantTypes.Add(type)); } /// /// Enables device authorization flow support. For more information about this /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc8628. /// /// The instance. public OpenIddictServerBuilder AllowDeviceAuthorizationFlow() => Configure(options => options.GrantTypes.Add(GrantTypes.DeviceCode)); /// /// Enables hybrid flow support. For more information /// about this specific OpenID Connect flow, visit /// http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth. /// /// The instance. public OpenIddictServerBuilder AllowHybridFlow() => Configure(options => { options.GrantTypes.Add(GrantTypes.AuthorizationCode); options.GrantTypes.Add(GrantTypes.Implicit); options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token); options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.Token); }); /// /// Enables implicit flow support. For more information /// about this specific OAuth 2.0/OpenID Connect flow, visit /// https://tools.ietf.org/html/rfc6749#section-4.2 and /// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth. /// /// /// Note: the implicit flow is not recommended for new applications due to /// its inherent limitations and should only be used in legacy scenarios. /// When possible, consider using the authorization code flow instead. /// /// The instance. public OpenIddictServerBuilder AllowImplicitFlow() => Configure(options => { options.GrantTypes.Add(GrantTypes.Implicit); options.ResponseTypes.Add(ResponseTypes.IdToken); options.ResponseTypes.Add(ResponseTypes.IdToken + ' ' + ResponseTypes.Token); options.ResponseTypes.Add(ResponseTypes.Token); }); /// /// Enables none flow support. For more information about this specific OAuth 2.0 flow, /// visit https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none. /// /// The instance. public OpenIddictServerBuilder AllowNoneFlow() => Configure(options => options.ResponseTypes.Add(ResponseTypes.None)); /// /// Enables password flow support. For more information about this specific /// OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. /// /// /// Note: the password flow is not recommended for new applications due to its /// inherent limitations and should only be used in legacy scenarios. When possible, /// consider using an interactive user flow like the authorization code flow instead. /// /// The instance. public OpenIddictServerBuilder AllowPasswordFlow() => Configure(options => options.GrantTypes.Add(GrantTypes.Password)); /// /// Enables refresh token flow support. For more information about this /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-6. /// /// The instance. public OpenIddictServerBuilder AllowRefreshTokenFlow() => Configure(options => { options.GrantTypes.Add(GrantTypes.RefreshToken); options.Scopes.Add(Scopes.OfflineAccess); }); /// /// Enables token exchange flow support. For more information about this /// specific OAuth 2.0 flow, visit https://datatracker.ietf.org/doc/html/rfc8693. /// /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder AllowTokenExchangeFlow() => Configure(options => options.GrantTypes.Add(GrantTypes.TokenExchange)); /// /// Sets the relative or absolute URIs associated to the authorization endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetAuthorizationEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetAuthorizationEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the authorization endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetAuthorizationEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.AuthorizationEndpointUris.Clear(); options.AuthorizationEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the configuration endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetConfigurationEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetConfigurationEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the configuration endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetConfigurationEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.ConfigurationEndpointUris.Clear(); options.ConfigurationEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the device authorization endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetDeviceAuthorizationEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetDeviceAuthorizationEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the device authorization endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetDeviceAuthorizationEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.DeviceAuthorizationEndpointUris.Clear(); options.DeviceAuthorizationEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the end session endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetEndSessionEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetEndSessionEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the end session endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetEndSessionEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.EndSessionEndpointUris.Clear(); options.EndSessionEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the introspection endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetIntrospectionEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetIntrospectionEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the introspection endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetIntrospectionEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.IntrospectionEndpointUris.Clear(); options.IntrospectionEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the JSON Web Key Set endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetJsonWebKeySetEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetJsonWebKeySetEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the JSON Web Key Set endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetJsonWebKeySetEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.JsonWebKeySetEndpointUris.Clear(); options.JsonWebKeySetEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the pushed authorization endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetPushedAuthorizationEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetPushedAuthorizationEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the pushed authorization endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetPushedAuthorizationEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.PushedAuthorizationEndpointUris.Clear(); options.PushedAuthorizationEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the revocation endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetRevocationEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetRevocationEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the revocation endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetRevocationEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.RevocationEndpointUris.Clear(); options.RevocationEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the token endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetTokenEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetTokenEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the token endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetTokenEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.TokenEndpointUris.Clear(); options.TokenEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the userinfo endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetUserInfoEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetUserInfoEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the userinfo endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned as part of the discovery document. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetUserInfoEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.UserInfoEndpointUris.Clear(); options.UserInfoEndpointUris.AddRange(uris); }); } /// /// Sets the relative or absolute URIs associated to the end-user verification endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned by the device authorization endpoint. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetEndUserVerificationEndpointUris( [StringSyntax(StringSyntaxAttribute.Uri)] params string[] uris) { ArgumentNullException.ThrowIfNull(uris); return SetEndUserVerificationEndpointUris([.. uris.Select(uri => new Uri(uri, UriKind.RelativeOrAbsolute))]); } /// /// Sets the relative or absolute URIs associated to the end-user verification endpoint. /// If an empty array is specified, the endpoint will be considered disabled. /// Note: only the first URI will be returned by the device authorization endpoint. /// /// The URIs associated to the endpoint. /// The instance. public OpenIddictServerBuilder SetEndUserVerificationEndpointUris(params Uri[] uris) { ArgumentNullException.ThrowIfNull(uris); if (Array.Exists(uris, OpenIddictHelpers.IsImplicitFileUri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uris)); } if (Array.Exists(uris, static uri => uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uris)); } return Configure(options => { options.EndUserVerificationEndpointUris.Clear(); options.EndUserVerificationEndpointUris.AddRange(uris); }); } /// /// Disables JWT access token encryption (this option doesn't affect Data Protection tokens). /// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens /// to third-party resource servers/APIs you don't control and don't fully trust. /// /// The instance. public OpenIddictServerBuilder DisableAccessTokenEncryption() => Configure(options => options.DisableAccessTokenEncryption = true); /// /// Allows processing authorization and token requests that specify audiences that /// have not been registered using . /// /// The instance. public OpenIddictServerBuilder DisableAudienceValidation() => Configure(options => options.DisableAudienceValidation = true); /// /// Disables authorization storage so that ad-hoc authorizations are /// not created when an authorization code or refresh token is issued /// and can't be revoked to prevent associated tokens from being used. /// Using this option is generally NOT recommended. /// /// The instance. public OpenIddictServerBuilder DisableAuthorizationStorage() => Configure(options => options.DisableAuthorizationStorage = true); /// /// Allows processing authorization and token requests that specify resources that /// have not been registered using . /// /// The instance. public OpenIddictServerBuilder DisableResourceValidation() => Configure(options => options.DisableResourceValidation = true); /// /// Configures OpenIddict to disable rolling refresh tokens so /// that refresh tokens used in a token request are not marked /// as redeemed and can still be used until they expire. Disabling /// rolling refresh tokens is NOT recommended, for security reasons. /// /// The instance. public OpenIddictServerBuilder DisableRollingRefreshTokens() => Configure(options => options.DisableRollingRefreshTokens = true); /// /// Allows processing authorization and token requests that specify scopes that have not /// been registered using or the scope manager. /// /// The instance. public OpenIddictServerBuilder DisableScopeValidation() => Configure(options => options.DisableScopeValidation = true); /// /// Disables sliding expiration. When using this option, refresh tokens /// are issued with a fixed expiration date: when they expire, a complete /// authorization flow must be started to retrieve a new refresh token. /// /// The instance. public OpenIddictServerBuilder DisableSlidingRefreshTokenExpiration() => Configure(options => options.DisableSlidingRefreshTokenExpiration = true); /// /// Disables token storage, so that no database entry is created /// for the tokens and codes returned by the OpenIddict server. /// Using this option is generally NOT recommended as it prevents /// the tokens and codes from being revoked (if needed). /// /// /// Note: disabling token storage prevents the device authorization flow /// from being used and automatically turns sliding expiration off. /// /// The instance. public OpenIddictServerBuilder DisableTokenStorage() => Configure(options => options.DisableTokenStorage = true); /// /// Enables the degraded mode. When the degraded mode is enabled, all the security checks that /// depend on the OpenIddict core managers are disabled. This option MUST be enabled with extreme /// caution and custom handlers MUST be registered to properly validate OpenID Connect requests. /// /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder EnableDegradedMode() => Configure(options => options.EnableDegradedMode = true); /// /// Disables audience permissions enforcement. Calling this method is NOT recommended. /// /// The instance. public OpenIddictServerBuilder IgnoreAudiencePermissions() => Configure(options => options.IgnoreAudiencePermissions = true); /// /// Disables endpoint permissions enforcement. Calling this method is NOT recommended. /// /// The instance. public OpenIddictServerBuilder IgnoreEndpointPermissions() => Configure(options => options.IgnoreEndpointPermissions = true); /// /// Disables grant type permissions enforcement. Calling this method is NOT recommended. /// /// The instance. public OpenIddictServerBuilder IgnoreGrantTypePermissions() => Configure(options => options.IgnoreGrantTypePermissions = true); /// /// Disables resource permissions enforcement. Calling this method is NOT recommended. /// /// The instance. public OpenIddictServerBuilder IgnoreResourcePermissions() => Configure(options => options.IgnoreResourcePermissions = true); /// /// Disables response type permissions enforcement. Calling this method is NOT recommended. /// /// The instance. public OpenIddictServerBuilder IgnoreResponseTypePermissions() => Configure(options => options.IgnoreResponseTypePermissions = true); /// /// Disables scope permissions enforcement. Calling this method is NOT recommended. /// /// The instance. public OpenIddictServerBuilder IgnoreScopePermissions() => Configure(options => options.IgnoreScopePermissions = true); /// /// Registers the specified audiences as supported audiences /// (exclusively used with the OAuth 2.0 Token Exchange flow). /// /// The supported audiences. /// The instance. public OpenIddictServerBuilder RegisterAudiences(params string[] audiences) { ArgumentNullException.ThrowIfNull(audiences); if (Array.Exists(audiences, string.IsNullOrEmpty)) { throw new ArgumentException(SR.FormatID0457(nameof(audiences)), nameof(audiences)); } return Configure(options => options.Audiences.UnionWith(audiences)); } /// /// Registers the specified claims as supported claims so /// they can be returned as part of the discovery document. /// /// The supported claims. /// The instance. public OpenIddictServerBuilder RegisterClaims(params string[] claims) { ArgumentNullException.ThrowIfNull(claims); if (Array.Exists(claims, string.IsNullOrEmpty)) { throw new ArgumentException(SR.FormatID0457(nameof(claims)), nameof(claims)); } return Configure(options => options.Claims.UnionWith(claims)); } /// /// Registers the specified prompt values as supported scopes so /// they can be returned as part of the discovery document. /// /// The supported prompt values. /// The instance. public OpenIddictServerBuilder RegisterPromptValues(params string[] values) { ArgumentNullException.ThrowIfNull(values); if (Array.Exists(values, string.IsNullOrEmpty)) { throw new ArgumentException(SR.FormatID0457(nameof(values)), nameof(values)); } return Configure(options => options.PromptValues.UnionWith(values)); } /// /// Registers the specified resources as supported resources (typically used /// with the OAuth 2.0 Token Exchange flow and with authorization or pushed /// authorization requests that include one or more resource indicators). /// /// The supported resources. /// The instance. public OpenIddictServerBuilder RegisterResources(params string[] resources) { ArgumentNullException.ThrowIfNull(resources); return RegisterResources([.. resources.Select(resource => new Uri(resource, UriKind.Absolute))]); } /// /// Registers the specified resources as supported resources (typically used /// with the OAuth 2.0 Token Exchange flow and with authorization or pushed /// authorization requests that include one or more resource indicators). /// /// The supported resources. /// The instance. public OpenIddictServerBuilder RegisterResources(params Uri[] resources) { ArgumentNullException.ThrowIfNull(resources); if (Array.Exists(resources, static resource => OpenIddictHelpers.IsImplicitFileUri(resource) || !string.IsNullOrEmpty(resource.Fragment))) { throw new ArgumentException(SR.FormatID0495(nameof(resources)), nameof(resources)); } return Configure(options => options.Resources.UnionWith(resources)); } /// /// Registers the specified scopes as supported scopes so /// they can be returned as part of the discovery document. /// /// The supported scopes. /// The instance. public OpenIddictServerBuilder RegisterScopes(params string[] scopes) { ArgumentNullException.ThrowIfNull(scopes); if (Array.Exists(scopes, string.IsNullOrEmpty)) { throw new ArgumentException(SR.FormatID0457(nameof(scopes)), nameof(scopes)); } return Configure(options => options.Scopes.UnionWith(scopes)); } /// /// Configures OpenIddict to force client applications to use Proof Key for Code Exchange /// (PKCE) when requesting an authorization code (e.g when using the code or hybrid flows). /// When enforced, authorization requests that lack the code_challenge parameter will be rejected. /// /// The instance. public OpenIddictServerBuilder RequireProofKeyForCodeExchange() => Configure(options => options.RequireProofKeyForCodeExchange = true); /// /// Configures OpenIddict to force client applications to use pushed authorization requests /// when using an interactive flow like the authorization code or implicit flows. /// When enforced, authorization requests that lack the request_id parameter will be rejected. /// /// The instance. public OpenIddictServerBuilder RequirePushedAuthorizationRequests() => Configure(options => options.RequirePushedAuthorizationRequests = true); /// /// Sets the access token lifetime, after which client applications must retrieve /// a new access token by making a grant_type=refresh_token token request /// or a prompt=none authorization request, depending on the selected flow. /// Using long-lived access tokens or tokens that never expire is not recommended. /// While discouraged, can be specified to issue tokens that never expire. /// /// The access token lifetime. /// The instance. public OpenIddictServerBuilder SetAccessTokenLifetime(TimeSpan? lifetime) => Configure(options => options.AccessTokenLifetime = lifetime); /// /// Sets the authorization code lifetime, after which client applications /// are unable to send a grant_type=authorization_code token request. /// Using short-lived authorization codes is strongly recommended. /// While discouraged, can be specified to issue codes that never expire. /// /// The authorization code lifetime. /// The instance. public OpenIddictServerBuilder SetAuthorizationCodeLifetime(TimeSpan? lifetime) => Configure(options => options.AuthorizationCodeLifetime = lifetime); /// /// Sets the device code lifetime, after which client applications are unable to /// send a grant_type=urn:ietf:params:oauth:grant-type:device_code token request. /// Using short-lived device codes is strongly recommended. /// While discouraged, can be specified to issue codes that never expire. /// /// The authorization code lifetime. /// The instance. public OpenIddictServerBuilder SetDeviceCodeLifetime(TimeSpan? lifetime) => Configure(options => options.DeviceCodeLifetime = lifetime); /// /// Sets the identity token lifetime, after which client /// applications should refuse processing identity tokens. /// While discouraged, can be specified to issue tokens that never expire. /// /// The identity token lifetime. /// The instance. public OpenIddictServerBuilder SetIdentityTokenLifetime(TimeSpan? lifetime) => Configure(options => options.IdentityTokenLifetime = lifetime); /// /// Sets the issued token lifetime, which is used as a fallback value when the /// issued token type isn't natively supported by OpenIddict (e.g an access token). /// While discouraged, can be specified to issue tokens that never expire. /// /// The issued token lifetime. /// The instance. public OpenIddictServerBuilder SetIssuedTokenLifetime(TimeSpan? lifetime) => Configure(options => options.IssuedTokenLifetime = lifetime); /// /// Sets the refresh token lifetime, after which client applications must get /// a new authorization from the user. When sliding expiration is enabled, /// a new refresh token is always issued to the client application, /// which prolongs the validity period of the refresh token. /// While discouraged, can be specified to issue tokens that never expire. /// /// The refresh token lifetime. /// The instance. public OpenIddictServerBuilder SetRefreshTokenLifetime(TimeSpan? lifetime) => Configure(options => options.RefreshTokenLifetime = lifetime); /// /// Sets the refresh token reuse leeway, during which rolling refresh tokens marked /// as redeemed can still be used to make concurrent refresh token requests. /// /// The refresh token reuse interval. /// The instance. public OpenIddictServerBuilder SetRefreshTokenReuseLeeway(TimeSpan? leeway) => Configure(options => options.RefreshTokenReuseLeeway = leeway); /// /// Sets the charset used by OpenIddict to generate random user codes. /// /// /// Note: user codes are meant to be entered manually by users. To ensure /// they remain easy enough to type even by users with non-Latin keyboards, /// user codes generated by OpenIddict only include ASCII digits by default. /// /// The charset used by OpenIddict to generate random user codes. /// The instance. public OpenIddictServerBuilder SetUserCodeCharset(params string[] charset) { ArgumentNullException.ThrowIfNull(charset); ArgumentOutOfRangeException.ThrowIfLessThan(charset.Length, 9, nameof(charset)); if (charset.Length != charset.Distinct(StringComparer.Ordinal).Count()) { throw new ArgumentException(SR.GetResourceString(SR.ID0436), nameof(charset)); } foreach (var character in charset) { #if SUPPORTS_TEXT_ELEMENT_ENUMERATOR // On supported platforms, ensure each character added to the // charset represents exactly one grapheme cluster/text element. var enumerator = StringInfo.GetTextElementEnumerator(character); if (!enumerator.MoveNext() || enumerator.MoveNext()) { throw new ArgumentException(SR.GetResourceString(SR.ID0437), nameof(charset)); } #else // On unsupported platforms, prevent non-ASCII characters from being used. if (character.Any(static character => (uint) character > '\x007f')) { throw new ArgumentException(SR.GetResourceString(SR.ID0438), nameof(charset)); } #endif } return Configure(options => { options.UserCodeCharset.Clear(); options.UserCodeCharset.UnionWith(charset); }); } /// /// Sets the format string used by OpenIddict to display user codes. While not recommended, /// a value can be used to disable the user code formatting logic. /// /// /// Note: if no value is explicitly set, a default format using dash separators /// is used to make user codes easier to read by the end users. /// /// The string used by OpenIddict to format user codes. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder SetUserCodeDisplayFormat(string? format) => Configure(options => options.UserCodeDisplayFormat = format); /// /// Sets the length of the user codes generated by OpenIddict (by default, 12 characters). /// /// The length of the user codes generated by OpenIddict. /// The instance. public OpenIddictServerBuilder SetUserCodeLength(int length) { ArgumentOutOfRangeException.ThrowIfLessThan(length, 6); return Configure(options => options.UserCodeLength = length); } /// /// Sets the user code lifetime, after which they'll no longer be considered valid. /// Using short-lived device codes is strongly recommended. /// While discouraged, can be specified to issue codes that never expire. /// /// The authorization code lifetime. /// The instance. public OpenIddictServerBuilder SetUserCodeLifetime(TimeSpan? lifetime) => Configure(options => options.UserCodeLifetime = lifetime); /// /// Sets the issuer URI, which is used as the value of the "issuer" claim and /// is returned from the discovery endpoint to identify the authorization server. /// /// The issuer URI. /// The instance. public OpenIddictServerBuilder SetIssuer(Uri uri) { ArgumentNullException.ThrowIfNull(uri); return Configure(options => options.Issuer = uri); } /// /// Sets the issuer URI, which is used as the value of the "issuer" claim and /// is returned from the discovery endpoint to identify the authorization server. /// /// The issuer URI. /// The instance. public OpenIddictServerBuilder SetIssuer( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(uri)); } return SetIssuer(value); } /// /// Sets the URI listed as the mTLS device authorization /// endpoint alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsDeviceAuthorizationEndpointAliasUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (OpenIddictHelpers.IsImplicitFileUri(uri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } if (uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uri)); } return Configure(options => options.MtlsDeviceAuthorizationEndpointAliasUri = uri); } /// /// Sets the URI listed as the mTLS device authorization /// endpoint alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsDeviceAuthorizationEndpointAliasUri( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } return SetMtlsDeviceAuthorizationEndpointAliasUri(value); } /// /// Sets the URI listed as the mTLS introspection /// endpoint alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsIntrospectionEndpointAliasUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (OpenIddictHelpers.IsImplicitFileUri(uri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } if (uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uri)); } return Configure(options => options.MtlsIntrospectionEndpointAliasUri = uri); } /// /// Sets the URI listed as the mTLS introspection endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsIntrospectionEndpointAliasUri( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } return SetMtlsIntrospectionEndpointAliasUri(value); } /// /// Sets the URI listed as the mTLS pushed authorization /// endpoint alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsPushedAuthorizationEndpointAliasUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (OpenIddictHelpers.IsImplicitFileUri(uri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } if (uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uri)); } return Configure(options => options.MtlsPushedAuthorizationEndpointAliasUri = uri); } /// /// Sets the URI listed as the mTLS pushed authorization /// endpoint alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsPushedAuthorizationEndpointAliasUri( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } return SetMtlsPushedAuthorizationEndpointAliasUri(value); } /// /// Sets the URI listed as the mTLS revocation endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsRevocationEndpointAliasUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (OpenIddictHelpers.IsImplicitFileUri(uri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } if (uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uri)); } return Configure(options => options.MtlsRevocationEndpointAliasUri = uri); } /// /// Sets the URI listed as the mTLS revocation endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsRevocationEndpointAliasUri( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } return SetMtlsRevocationEndpointAliasUri(value); } /// /// Sets the URI listed as the mTLS token endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsTokenEndpointAliasUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (OpenIddictHelpers.IsImplicitFileUri(uri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } if (uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uri)); } return Configure(options => options.MtlsTokenEndpointAliasUri = uri); } /// /// Sets the URI listed as the mTLS token endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsTokenEndpointAliasUri( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } return SetMtlsTokenEndpointAliasUri(value); } /// /// Sets the URI listed as the mTLS userinfo endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsUserInfoEndpointAliasUri(Uri uri) { ArgumentNullException.ThrowIfNull(uri); if (OpenIddictHelpers.IsImplicitFileUri(uri)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } if (uri.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(SR.FormatID0081("~"), nameof(uri)); } return Configure(options => options.MtlsUserInfoEndpointAliasUri = uri); } /// /// Sets the URI listed as the mTLS userinfo endpoint /// alias in the server configuration metadata. /// /// /// Note: this URI MUST be absolute and MUST point to a domain for /// which TLS client authentication is enforced by the web server. /// /// The endpoint URI. /// The instance. public OpenIddictServerBuilder SetMtlsUserInfoEndpointAliasUri( [StringSyntax(StringSyntaxAttribute.Uri, UriKind.Absolute)] string uri) { ArgumentException.ThrowIfNullOrEmpty(uri); if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || OpenIddictHelpers.IsImplicitFileUri(value)) { throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(uri)); } return SetMtlsUserInfoEndpointAliasUri(value); } /// /// Configures OpenIddict to use reference tokens, so that the access token payloads /// are stored in the database (only an identifier is returned to the client application). /// Enabling this option is useful when storing a very large number of claims in the tokens, /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core /// Data Protection integration, that provides additional protection against token leakage. /// /// The instance. public OpenIddictServerBuilder UseReferenceAccessTokens() => Configure(options => options.UseReferenceAccessTokens = true); /// /// Configures OpenIddict to use reference tokens, so that the refresh token payloads /// are stored in the database (only an identifier is returned to the client application). /// Enabling this option is useful when storing a very large number of claims in the tokens, /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core /// Data Protection integration, that provides additional protection against token leakage. /// /// The instance. public OpenIddictServerBuilder UseReferenceRefreshTokens() => Configure(options => options.UseReferenceRefreshTokens = true); /// /// Configures OpenIddict to bind access tokens to the client certificates client certificate /// sent by public or confidential clients in the TLS handshake of token requests. /// /// The instance. public OpenIddictServerBuilder UseClientCertificateBoundAccessTokens() => Configure(options => options.UseClientCertificateBoundAccessTokens = true); /// /// Configures OpenIddict to bind refresh tokens to the client certificates client /// certificate sent by public clients in the TLS handshake of token requests. /// /// /// Note: refresh tokens are only bound to the client certificate when the client /// is a public application, as refresh tokens issued to confidential applications /// are already sender-constrained via standard client authentication. /// /// The instance. public OpenIddictServerBuilder UseClientCertificateBoundRefreshTokens() => Configure(options => options.UseClientCertificateBoundRefreshTokens = true); /// /// Enables authorization request storage, so that authorization requests /// are automatically stored in the token store, which allows flowing /// large payloads across requests. Enabling this option can be useful /// for clients that do not supported pushed authorization requests. /// /// The instance. public OpenIddictServerBuilder EnableAuthorizationRequestCaching() => Configure(options => options.EnableAuthorizationRequestCaching = true); /// /// Enables end session request storage, so that end session requests /// are automatically stored in the token store, which allows flowing /// large payloads across requests. /// /// The instance. public OpenIddictServerBuilder EnableEndSessionRequestCaching() => Configure(options => options.EnableEndSessionRequestCaching = true); /// /// Configures OpenIddict to enable PKI client certificate authentication (mTLS) and trust /// the specified root and intermediate certificates when validating client certificates. /// /// The store containing the root and intermediate certificates to trust. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder EnablePublicKeyInfrastructureTlsClientAuthentication(X509Certificate2Collection certificates) => EnablePublicKeyInfrastructureTlsClientAuthentication(certificates, static policy => { }); /// /// Configures OpenIddict to enable PKI client certificate authentication (mTLS) and trust /// the specified root and intermediate certificates when validating client certificates. /// /// The store containing the root and intermediate certificates to trust. /// The delegate used to amend the created X.509 chain policy. /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder EnablePublicKeyInfrastructureTlsClientAuthentication( X509Certificate2Collection certificates, Action configuration) { ArgumentNullException.ThrowIfNull(certificates); ArgumentNullException.ThrowIfNull(configuration); #if SUPPORTS_X509_CHAIN_POLICY_CUSTOM_TRUST_STORE // Ensure at least one root certificate authority was included in the certificate collection. if (!certificates.Cast().Any(static certificate => OpenIddictHelpers.IsCertificateAuthority(certificate) && OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign) && OpenIddictHelpers.IsSelfIssuedCertificate(certificate))) { throw new ArgumentException(SR.GetResourceString(SR.ID0507), nameof(certificates)); } // Ensure no end certificate was included in the certificate collection. if (certificates.Cast().Any(static certificate => !OpenIddictHelpers.IsCertificateAuthority(certificate) || !OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign))) { throw new ArgumentException(SR.GetResourceString(SR.ID0501), nameof(certificates)); } // Ensure none of the certificates contains a private key. if (certificates.Cast().Any(static certificate => certificate.HasPrivateKey)) { throw new ArgumentException(SR.GetResourceString(SR.ID0511), nameof(certificates)); } var policy = new X509ChainPolicy { // Note: by default, OpenIddict requires that end certificates used for TLS client // authentication explicitly list client authentication as an allowed extended key usage. ApplicationPolicy = { new Oid(ObjectIdentifiers.ExtendedKeyUsages.ClientAuthentication) }, TrustMode = X509ChainTrustMode.CustomRootTrust }; policy.CustomTrustStore.AddRange(certificates); // If one of the root certificates doesn't include a CRL or AIA // extension, ignore root revocation unknown status errors by default. if (certificates.Cast() .Where(static certificate => OpenIddictHelpers.IsCertificateAuthority(certificate) && OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign) && OpenIddictHelpers.IsSelfIssuedCertificate(certificate)) .Any(static certificate => certificate.Extensions[ObjectIdentifiers.CertificateExtensions.CrlDistributionPoints] is null && certificate.Extensions[ObjectIdentifiers.CertificateExtensions.AuthorityInfoAccess] is null)) { policy.VerificationFlags |= X509VerificationFlags.IgnoreRootRevocationUnknown; } // If one of the intermediate certificates doesn't include a CRL or AIA // extension, ignore root revocation unknown status errors by default. if (certificates.Cast() .Where(static certificate => OpenIddictHelpers.IsCertificateAuthority(certificate) && OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign) && !OpenIddictHelpers.IsSelfIssuedCertificate(certificate)) .Any(static certificate => certificate.Extensions[ObjectIdentifiers.CertificateExtensions.CrlDistributionPoints] is null && certificate.Extensions[ObjectIdentifiers.CertificateExtensions.AuthorityInfoAccess] is null)) { policy.VerificationFlags |= X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown; } // If the root or intermediate certificates doesn't include a CRL or AIA, assume // by default that the end certificates won't have a CRL or AIA extension either. if (certificates.Cast() .Where(static certificate => OpenIddictHelpers.IsCertificateAuthority(certificate) && OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign)) .Any(static certificate => certificate.Extensions[ObjectIdentifiers.CertificateExtensions.CrlDistributionPoints] is null && certificate.Extensions[ObjectIdentifiers.CertificateExtensions.AuthorityInfoAccess] is null)) { policy.VerificationFlags |= X509VerificationFlags.IgnoreEndRevocationUnknown; } // Run the user-provided configuration delegate and ensure the trust mode wasn't accidentally changed to // prevent spoofing attacks (i.e attacks that consist in using a client certificate issued by a certificate // authority trusted by the operating system but that isn't the one expected by the authorization server // for client authentication). While discouraged, applications that need to use the system root store // (e.g applications running on .NET Framework) can manually attach a custom policy to the server options. configuration(policy); if (policy.TrustMode is not X509ChainTrustMode.CustomRootTrust) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0509)); } return Configure(options => options.PublicKeyInfrastructureTlsClientAuthenticationPolicy = policy); #else throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0508)); #endif } /// /// Configures OpenIddict to enable self-signed TLS client authentication (mTLS). /// /// The instance. [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder EnableSelfSignedTlsClientAuthentication() => EnableSelfSignedTlsClientAuthentication(static policy => { }); /// /// Configures OpenIddict to enable self-signed TLS client authentication (mTLS). /// /// The delegate used to amend the created X.509 chain policy. /// The instance. public OpenIddictServerBuilder EnableSelfSignedTlsClientAuthentication(Action configuration) { ArgumentNullException.ThrowIfNull(configuration); #if SUPPORTS_X509_CHAIN_POLICY_CUSTOM_TRUST_STORE var policy = new X509ChainPolicy { // Note: by default, OpenIddict requires that end certificates used for TLS client // authentication explicitly list client authentication as an allowed extended key usage. ApplicationPolicy = { new Oid(ObjectIdentifiers.ExtendedKeyUsages.ClientAuthentication) }, // Note: self-signed TLS client certificates typically do not include revocation information // (CRL or AIA) and are "revoked" by simply being removed from the JSON Web Key Set. RevocationMode = X509RevocationMode.NoCheck, TrustMode = X509ChainTrustMode.CustomRootTrust }; // Run the user-provided configuration delegate and ensure the trust mode wasn't accidentally changed to // prevent spoofing attacks (i.e attacks that consist in using a client certificate issued by a certificate // authority trusted by the operating system but that isn't the one expected by the authorization server // for client authentication). While discouraged, applications that need to use the system root store // (e.g applications running on .NET Framework) can manually attach a custom policy to the server options. configuration(policy); if (policy.TrustMode is not X509ChainTrustMode.CustomRootTrust) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0509)); } return Configure(options => options.SelfSignedTlsClientAuthenticationPolicy = policy); #else throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0508)); #endif } /// [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object? obj) => base.Equals(obj); /// [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => base.GetHashCode(); /// [EditorBrowsable(EditorBrowsableState.Never)] public override string? ToString() => base.ToString(); }