diff --git a/.editorconfig b/.editorconfig index 7357f91a8..e1e371638 100644 --- a/.editorconfig +++ b/.editorconfig @@ -473,9 +473,6 @@ dotnet_diagnostic.CA1062.severity = none # CA1303: Do not pass literals as localized parameters dotnet_diagnostic.CA1303.severity = none -# SA1004: Documentation lines should begin with single space -dotnet_diagnostic.SA1004.severity = none - # SA1118: Parameter should not span multiple lines dotnet_diagnostic.SA1118.severity = none @@ -536,9 +533,6 @@ dotnet_diagnostic.SA1616.severity = none # SA1623: Property summary documentation should match accessors dotnet_diagnostic.SA1623.severity = none -# SA1627: Documentation text should not be empty -dotnet_diagnostic.SA1627.severity = none - # SA1642: Constructor summary documentation should begin with standard text dotnet_diagnostic.SA1642.severity = none diff --git a/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs b/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs index eab50eb74..dcf9d2005 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs @@ -26,10 +26,10 @@ public static CertificateDescription FromCertificate(X509Certificate2 x509certif } /// - /// Creates a Certificate Description from KeyVault. + /// Creates a certificate description from Key Vault. /// - /// - /// + /// The Key Vault URL. + /// The name of the certificate in Key Vault. /// A certificate description. public static CertificateDescription FromKeyVault(string keyVaultUrl, string keyVaultCertificateName) { @@ -42,9 +42,9 @@ public static CertificateDescription FromKeyVault(string keyVaultUrl, string key } /// - /// Create a certificate description from a base 64 encoded value. + /// Create a certificate description from a Base64 encoded value. /// - /// base 64 encoded value. + /// Base64 encoded certificate value. /// A certificate description. public static CertificateDescription FromBase64Encoded(string base64EncodedValue) { @@ -59,7 +59,7 @@ public static CertificateDescription FromBase64Encoded(string base64EncodedValue /// Create a certificate description from path on disk. /// /// Path were to find the certificate file. - /// certificate password. + /// Certificate password. /// A certificate description. public static CertificateDescription FromPath(string path, string password = null) { @@ -72,7 +72,7 @@ public static CertificateDescription FromPath(string path, string password = nul } /// - /// Create a certificate description from a thumbprint and store location (certificate manager on Windows for instance). + /// Create a certificate description from a thumbprint and store location (Certificate Manager on Windows for instance). /// /// Certificate thumbprint. /// Store location where to find the certificate. @@ -93,7 +93,7 @@ public static CertificateDescription FromStoreWithThumprint( /// /// Create a certificate description from a certificate distinguished name (such as CN=name) - /// and store location (certificate manager on Windows for instance). + /// and store location (Certificate Manager on Windows for instance). /// /// Certificate distinguished named. /// Store location where to find the certificate. @@ -121,14 +121,14 @@ public static CertificateDescription FromStoreWithDistinguishedName( /// Container in which to find the certificate. /// /// If equals , then - /// the container is the KeyVault base URL + /// the container is the Key Vault base URL. /// If equals , then - /// this value is not used + /// this value is not used. /// If equals , then - /// this value is the path on disk where to find the certificate + /// this value is the path on disk where to find the certificate. /// If equals , /// or , then - /// this value is the path to the certificate in the cert store, for instance CurrentUser/My + /// this value is the path to the certificate in the cert store, for instance CurrentUser/My. /// /// internal string Container @@ -177,12 +177,12 @@ internal string Container } /// - /// URL of the KeyVault for instance https://msidentitywebsamples.vault.azure.net. + /// URL of the Key Vault for instance https://msidentitywebsamples.vault.azure.net. /// public string KeyVaultUrl { get; set; } /// - /// Certiticate store path, for instance "CurrentUser/My". + /// Certificate store path, for instance "CurrentUser/My". /// /// This property should only be used in conjunction with DistinguishName or Thumbprint. public string CertificateStorePath { get; set; } @@ -193,7 +193,7 @@ internal string Container public string CertificateDistinguishedName { get; set; } /// - /// Name of the certificate in KeyVault. + /// Name of the certificate in Key Vault. /// public string KeyVaultCertificateName { get; set; } @@ -213,7 +213,7 @@ internal string Container public string CertificatePassword { get; set; } /// - /// Base 64 encoded value. + /// Base64 encoded certificate value. /// public string Base64EncodedValue { get; set; } @@ -222,11 +222,11 @@ internal string Container /// /// /// If equals , then - /// the reference is the name of the certificate in KeyVault (maybe the version?) + /// the reference is the name of the certificate in Key Vault (maybe the version?). /// If equals , then - /// this value is the base 64 encoded certificate itself + /// this value is the base 64 encoded certificate itself. /// If equals , then - /// this value is the password to access the certificate (if needed) + /// this value is the password to access the certificate (if needed). /// If equals , /// this value is the distinguished name. /// If equals , @@ -281,7 +281,7 @@ internal string ReferenceOrValue } /// - /// The certificate, either provided directly in code by the + /// The certificate, either provided directly in code /// or loaded from the description. /// public X509Certificate2 Certificate { get; internal set; } diff --git a/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs b/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs index 75447c23a..97827eeee 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs @@ -9,22 +9,22 @@ namespace Microsoft.Identity.Web public enum CertificateSource { /// - /// Certificate itself + /// Certificate itself. /// Certificate = 0, /// - /// KeyVault + /// From an Azure Key Vault. /// KeyVault = 1, /// - /// Base 64 encoded directly in the configuration. + /// Base64 encoded string directly from the configuration. /// Base64Encoded = 2, /// - /// Local path on disk + /// From local path on disk. /// Path = 3, @@ -34,7 +34,7 @@ public enum CertificateSource StoreWithThumbprint = 4, /// - /// From the certificate store, described by its Distinguished name. + /// From the certificate store, described by its distinguished name. /// StoreWithDistinguishedName = 5, } diff --git a/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs b/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs index 212a32e6e..d682d36e4 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs @@ -54,9 +54,9 @@ private static X509Certificate2 LoadFromBase64Encoded(string certificateBase64) } /// - /// Load a certificate from KeyVault, including the private key. + /// Load a certificate from Key Vault, including the private key. /// - /// Url of KeyVault. + /// URL of Key Vault. /// Name of the certificate. /// An certificate. /// This code is inspired by Heath Stewart's code in: diff --git a/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs b/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs index 949540e09..b21efd06a 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs @@ -4,7 +4,7 @@ namespace Microsoft.Identity.Web { /// - /// Interface to implement load a certificate. + /// Interface to implement loading of a certificate. /// internal interface ICertificateLoader { diff --git a/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs b/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs index 7cf320790..658ea3f61 100644 --- a/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs +++ b/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs @@ -46,7 +46,7 @@ internal interface ITokenAcquisitionInternal /// Removes the account associated with context.HttpContext.User from the MSAL.NET cache. /// /// RedirectContext passed-in to a - /// Openidconnect event. + /// OpenID Connect event. /// Task RemoveAccountAsync(RedirectContext context); } diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs index f19cb89f6..30f73b8e9 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.Identity.Web { @@ -29,28 +30,37 @@ public class MicrosoftIdentityOptions : OpenIdConnectOptions /// /// In a web app, gets or sets the RedirectUri (URI where the token will be sent back by - /// Azure Active Directory or Azure Active Directory B2C) + /// Azure Active Directory or Azure Active Directory B2C). /// This property is exclusive with which should be used preferably if you don't want /// to have a different deployed configuration from your developer configuration. /// There are cases where RedirectUri is needed, for instance when you use a reverse proxy that transforms HTTPS /// URLs (external world) to HTTP URLs (inside the protected area). This can also be useful for web apps running - /// in containers (for the same reasons) + /// in containers (for the same reasons). /// If you don't specify the redirect URI, the redirect URI will be computed from the URL on which the app is /// deployed and the CallbackPath. /// public string RedirectUri { get; set; } /// - /// In a web app, gets or sets the PostLogoutRedirectUri + /// In a web app, gets or sets the PostLogoutRedirectUri. /// This property is exclusive with which should be used preferably if you don't want /// to have a different deployed configuration from your developer configuration. /// There are cases where PostLogoutRedirectUri is needed, for instance when you use a reverse proxy that transforms HTTPS /// URLs (external world) to HTTP URLs (inside the protected area). This can also be useful for web apps running - /// in containers (for the same reasons) + /// in containers (for the same reasons). /// If you don't specify the PostLogoutRedirectUri, it will be computed by ASP.NET Core using the SignedOutCallbackPath. /// public string PostLogoutRedirectUri { get; set; } + /// + /// When set to true, forces the and the to use the HTTPS scheme. + /// This behavior can be desired, for instance, when you use a reverse proxy that transforms HTTPS + /// URLs (external world) to HTTP URLs (inside the protected area). This can also be useful for web apps running + /// in containers (for the same reasons), for example when deploying your web app to + /// Azure App Services in Linux containers. + /// + public bool ForceHttpsRedirectUris { get; set; } + /// /// Gets or sets TokenAcquisition as a Singleton. There are scenarios, like using the Graph SDK, /// which require TokenAcquisition to be a Singleton. diff --git a/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs index 4e701a249..8c27cbf3c 100644 --- a/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs +++ b/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs @@ -24,8 +24,9 @@ public static class ScopesRequiredHttpContextExtensions /// /// HttpContext (from the controller). /// Scopes accepted by this web API. - /// with a set to - /// + /// with a set to + /// . + /// public static void VerifyUserHasAnyAcceptedScope(this HttpContext context, params string[] acceptedScopes) { if (acceptedScopes == null) diff --git a/src/Microsoft.Identity.Web/TokenAcquisition.cs b/src/Microsoft.Identity.Web/TokenAcquisition.cs index e3f81cf85..7385e6f05 100644 --- a/src/Microsoft.Identity.Web/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/TokenAcquisition.cs @@ -284,7 +284,7 @@ public async Task GetAccessTokenForAppAsync(IEnumerable scopes) /// Removes the account associated with context.HttpContext.User from the MSAL.NET cache. /// /// RedirectContext passed-in to a - /// Openidconnect event. + /// OpenID Connect event. /// public async Task RemoveAccountAsync(RedirectContext context) { diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs index 91822177e..06cc6aaa7 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs @@ -10,21 +10,24 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Session { /// - /// An implementation of token cache for Confidential clients backed by an HTTP session. + /// An implementation of token cache for confidential clients backed by an HTTP session. /// + /// /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state /// - /// // In the method - public void ConfigureServices(IServiceCollection services) in startup.cs, add the following + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// /// services.AddSession(option => /// { /// option.Cookie.IsEssential = true; /// }); - /// - /// In the method - public void Configure(IApplicationBuilder app, IHostingEnvironment env) in startup.cs, add the following - /// + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// /// app.UseSession(); // Before UseMvc() - /// + /// + /// /// https://aka.ms/msal-net-token-cache-serialization public class MsalSessionTokenCacheProvider : MsalAbstractTokenCacheProvider, IMsalTokenCacheProvider { diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs index 21529bf97..cb3fdd391 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs @@ -12,20 +12,25 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Session /// public static class SessionTokenCacheProviderExtension { - /// Adds both App and per-user session token caches. + /// + /// Adds both App and per-user session token caches. + /// + /// /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state /// - /// // In the method - public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// /// services.AddSession(option => /// { /// option.Cookie.IsEssential = true; /// }); - /// - /// In the method - public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following - /// + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// /// app.UseSession(); // Before UseMvc() - /// + /// + /// /// The services collection to add to. /// The service collection. public static IServiceCollection AddSessionTokenCaches(this IServiceCollection services) @@ -56,20 +61,25 @@ public static IServiceCollection AddSessionTokenCaches(this IServiceCollection s return services; } - /// Adds an HTTP session based application token cache to the service collection. + /// + /// Adds an HTTP session based application token cache to the service collection. + /// + /// /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state /// - /// // In the method - public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// /// services.AddSession(option => /// { /// option.Cookie.IsEssential = true; /// }); - /// - /// In the method - public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following - /// + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// /// app.UseSession(); // Before UseMvc() - /// + /// + /// /// The services collection to add to. /// The service collection. public static IServiceCollection AddSessionAppTokenCache(this IServiceCollection services) @@ -79,20 +89,25 @@ public static IServiceCollection AddSessionAppTokenCache(this IServiceCollection return services; } - /// Adds an HTTP session based per user token cache to the service collection. + /// + /// Adds an HTTP session based per user token cache to the service collection. + /// + /// /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state /// - /// // In the method - public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// /// services.AddSession(option => /// { /// option.Cookie.IsEssential = true; /// }); - /// - /// In the method - public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following - /// + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// /// app.UseSession(); // Before UseMvc() - /// + /// + /// /// The services collection to add to. /// The service collection. public static IServiceCollection AddSessionPerUserTokenCache(this IServiceCollection services) diff --git a/src/Microsoft.Identity.Web/WebAppAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebAppAuthenticationBuilderExtensions.cs index a1f3eb742..82134953c 100644 --- a/src/Microsoft.Identity.Web/WebAppAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/WebAppAuthenticationBuilderExtensions.cs @@ -155,6 +155,11 @@ public static AuthenticationBuilder AddSignIn( } } + if (microsoftIdentityOptions.ForceHttpsRedirectUris && (context.ProtocolMessage?.RedirectUri?.StartsWith("http://") ?? false)) + { + context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri.Replace("http://", "https://"); + } + await redirectToIdpHandler(context).ConfigureAwait(false); }; @@ -171,6 +176,11 @@ public static AuthenticationBuilder AddSignIn( context.ProtocolMessage.PostLogoutRedirectUri = microsoftIdentityOptions.PostLogoutRedirectUri; } } + + if (microsoftIdentityOptions.ForceHttpsRedirectUris && (context.ProtocolMessage?.PostLogoutRedirectUri?.StartsWith("http://") ?? false)) + { + context.ProtocolMessage.PostLogoutRedirectUri = context.ProtocolMessage.PostLogoutRedirectUri.Replace("http://", "https://"); + } }; if (microsoftIdentityOptions.IsB2C) diff --git a/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs b/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs index d2ef5b6c0..e6d7a95e7 100644 --- a/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs +++ b/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs @@ -341,6 +341,47 @@ public void AddWebAppCallsProtectedWebApi_NoScopes() Assert.Contains(OidcConstants.ScopeProfile, oidcOptions.Scope); } + [Theory] + [InlineData(true, "http://localhost:123", "https://localhost:123")] + [InlineData(true, "https://localhost:123", "https://localhost:123")] + [InlineData(false, "http://localhost:123", "http://localhost:123")] + [InlineData(false, "https://localhost:123", "https://localhost:123")] + public async void AddSignIn_ForceHttpsRedirectUris(bool forceHttpsRedirectUris, string actualUri, string expectedUri) + { + _configureMsOptions = (options) => + { + options.Instance = TestConstants.AadInstance; + options.TenantId = TestConstants.TenantIdAsGuid; + options.ClientId = TestConstants.ClientId; + options.ForceHttpsRedirectUris = forceHttpsRedirectUris; + }; + + var services = new ServiceCollection(); + services.AddDataProtection(); + new AuthenticationBuilder(services) + .AddSignIn(_configureOidcOptions, _configureMsOptions, _oidcScheme, _cookieScheme); + + var provider = services.BuildServiceProvider(); + + var oidcOptions = provider.GetRequiredService>().Create(_oidcScheme); + + var (httpContext, authScheme, authProperties) = CreateContextParameters(provider); + var redirectContext = new RedirectContext(httpContext, authScheme, oidcOptions, authProperties) + { + ProtocolMessage = new OpenIdConnectMessage() + { + RedirectUri = actualUri, + PostLogoutRedirectUri = actualUri, + }, + }; + + await oidcOptions.Events.RedirectToIdentityProvider(redirectContext).ConfigureAwait(false); + await oidcOptions.Events.RedirectToIdentityProviderForSignOut(redirectContext).ConfigureAwait(false); + + Assert.Equal(expectedUri, redirectContext.ProtocolMessage.RedirectUri); + Assert.Equal(expectedUri, redirectContext.ProtocolMessage.PostLogoutRedirectUri); + } + private void AddSignIn_TestCommon(IServiceCollection services, ServiceProvider provider) { // Assert correct services added