0

I have a legacy ASP.NET 4.8 MVC application which is being migrated to use Azure AD Authentication using the Microsoft.Owin framework. The application configuration works fine on my local machine. However, I encounter issues when running it on the production server.

Production environment proxy Setup: The application is hosted on an IIS server behind a proxy. SSL is terminated at the proxy, and HTTP requests are forwarded to the IIS server.

When I browse protected pages on the production server, I am correctly redirected to the login.microsoftonline.com login page and can log in successfully. However, after logging in, the .AspNet.Cookies authentication cookie is not set on the production server, resulting in a redirect loop between the application and the Azure AD /authorize endpoint.

I am assuming there is a problem issuing ".AspNet.Cookies" cookie as I don't see that in my browser. How can this be confirmed? or traced?

This is how it is configured in startup.cs class. I have followed some online article to implement this & working fine on my local.

public void Configuration(IAppBuilder app)
{
    // Configure Auth0 parameters
    string auth0Domain = ConfigurationManager.AppSettings["auth0:Domain"];
    string auth0ClientId = ConfigurationManager.AppSettings["auth0:ClientId"];
    string auth0RedirectUri = ConfigurationManager.AppSettings["auth0:RedirectUri"];
    string auth0PostLogoutRedirectUri = ConfigurationManager.AppSettings["auth0:PostLogoutRedirectUri"];

    // Set Cookies as default authentication type
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
        LoginPath = new PathString("/Account/Login"),
        CookieSameSite = Microsoft.Owin.SameSiteMode.None,
        // More information on why the CookieManager needs to be set can be found here: 
        // https://github.com/aspnet/AspNetKatana/wiki/System.Web-response-cookie-integration-issues
        CookieManager = new SameSiteCookieManager(new SystemWebCookieManager())
    });


    // Configure Auth0 authentication
    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        AuthenticationType = "Auth0",

        Authority = auth0Domain,

        ClientId = auth0ClientId,

        RedirectUri = auth0RedirectUri,
        PostLogoutRedirectUri = auth0PostLogoutRedirectUri,
        ResponseType = OpenIdConnectResponseType.CodeIdToken,
        Scope = "openid profile email",

        SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,

        TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name"
        },

        // More information on why the CookieManager needs to be set can be found here: 
        // https://docs.microsoft.com/en-us/aspnet/samesite/owin-samesite
        CookieManager = new SameSiteCookieManager(new SystemWebCookieManager()),

        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            AuthorizationCodeReceived = async n =>
            {
                var client = new HttpClient();
                var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest
                {
                    Code = n.Code,
                    Address = "https://login.microsoftonline.com/2d55c52b-2865-45c0-b174-fef7eed8e331/oauth2/v2.0/token",
                    ClientId = auth0ClientId,
                    ClientSecret = "WLT8Q~KKe2S5k~XrzUsz-XJUiRPdgaiBAx1uYcnr",
                    RedirectUri = auth0RedirectUri,
                });

                if (tokenResponse.IsError)
                    throw new Exception(tokenResponse.Error);

                var response = await client.GetUserInfoAsync(new UserInfoRequest
                {
                    Token = tokenResponse.AccessToken,
                    Address = "https://graph.microsoft.com/oidc/userinfo",
                });

                if (response.IsError)
                    throw new Exception(response.Error);

                n.AuthenticationTicket.Identity.AddClaims(response.Claims);
            },
            RedirectToIdentityProvider = notification =>
            {
                if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                {

                }
                if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                {
                    var logoutUri = $"https://{auth0Domain}/v2/logout?client_id={auth0ClientId}";

                    var postLogoutUri = notification.ProtocolMessage.PostLogoutRedirectUri;
                    if (!string.IsNullOrEmpty(postLogoutUri))
                    {
                        if (postLogoutUri.StartsWith("/"))
                        {
                            // transform to absolute
                            var request = notification.Request;
                            postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
                        }
                        logoutUri += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
                    }

                    notification.Response.Redirect(logoutUri);
                    notification.HandleResponse();
                }
                return Task.FromResult(0);
            }
        }
    });
}

How can I ensure the .AspNet.Cookies cookie is set correctly on the production server?

Are there additional configurations needed to handle SSL termination at the proxy?

Edit: added web.config file

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  https://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <section name="sdx.logging" type="SDX.Logging.SDXLoggingConfiguration, SDX.System" />
    <section name="sdx.services" type="SDX.Services.SDXServicesConfiguration, SDX.Services" />
    <section name="sdx.resources" type="SDX.Resources.SDXResourcesConfiguration, SDX.Resources" />
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="true" />
    <add key="PreserveLoginUrl" value="true" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    
    <add key="auth0:Domain" value="https://login.microsoftonline.com/xxxx/v2.0" />
    <add key="auth0:ClientId" value="f14563a5c4" />
    <add key="auth0:RedirectUri" value="http://localhost:49141" />
    <add key="auth0:PostLogoutRedirectUri" value="http://localhost:49141" />
  </appSettings>
  <connectionStrings>
  </connectionStrings>
  <log4net debug="true">
  <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="logs\log.txt" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="10MB" />
    <datePattern value="yyyyMMdd" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %-5level %logger [%ndc] – %message%newline" />
    </layout>
  </appender>

  <root>
    <level value="DEBUG" />
    <appender-ref ref="RollingLogFileAppender" />
  </root>
</log4net>
  <!--
      maxLogAgeDays = How many days of logs to keep. Max=7.
      traceLevel = 
        Error: Output error-handling messages.
        Warning: Output warnings and error-handling messages.
        Info: Output informational messages, warnings, and error-handling messages.
        Verbose: Output all debugging and tracing messages.
    -->
  <sdx.logging traceLevel="Verbose" cleanupInterval="1.00:00:00" logFilename="C:\SourceCodes\VSTS\Logs\%d\web.log">
    <cleanupDirectories>
      <add name="C:\SourceCodes\VSTS\Logs\" />
    </cleanupDirectories>
  </sdx.logging>
  
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.8" />
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />
        <bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Web.Infrastructure" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-2.0.1.0" newVersion="2.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-5.2.9.0" newVersion="5.2.9.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.Logging" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.5.2.0" newVersion="7.5.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.5.2.0" newVersion="7.5.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.JsonWebTokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.5.2.0" newVersion="7.5.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.Protocols" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.5.2.0" newVersion="7.5.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.Protocols.OpenIdConnect" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.5.2.0" newVersion="7.5.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.5.2.0" newVersion="7.5.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
  </entityFramework>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="ServiceEndpointBehavior">
          <dataContractSerializer maxItemsInObjectGraph="2147483647" />
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="None" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
        <behavior name="MyServiceBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" />
    <bindings>
      <netTcpBinding>
        <binding name="NetTcpBinding" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxConnections="10" maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
          <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
          <security mode="None">
            <message clientCredentialType="None" />
          </security>
        </binding>
      </netTcpBinding>
    </bindings>
    <client>
      <endpoint name="NetTcpBinding_IMySampleAppService" address="net.tcp://localhost:8585/MySampleAppService" binding="netTcpBinding" bindingConfiguration="NetTcpBinding" contract="SDX.GCPS.Models.IMySampleAppService" behaviorConfiguration="ServiceEndpointBehavior">
        <identity>
          <servicePrincipalName value="hostname/localhost" />
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

Edit: 07/09/2024 below is the samesite cookie error I see in chrome. Cookie Error Image

Code added in global.asax

protected void Application_BeginRequest(object sender, EventArgs e)
{
    HttpApplication application = sender as HttpApplication;
    if (application != null)
    {
        var userAgent = application.Context.Request.UserAgent;
        application.Response.AddOnSendingHeaders(context =>
        {
            var cookies = context.Response.Cookies;
            for (var i = 0; i < cookies.Count; i++)
            {
                var cookie = cookies[i];

                if (cookie.Name.ToLower().Contains("nonce"))
                    cookie.SameSite = SameSiteMode.None;                        
            }
        });
    }
}
7
  • Are you Deploying your app to the Local IIs server Or Azure VM IIs Server? Commented May 24 at 7:02
  • @AsleshaKantamsetti I tried on both on premise windows server (this one is behind proxy) & IIS on azure VM (this is behind a gateway) both fails to create the cookie. I see the session is active on IdP because it doesn't ask me for any credentials later.
    – Jay
    Commented May 24 at 11:40
  • If Possible, provide web.config file? Commented May 24 at 12:55
  • @AsleshaKantamsetti added web.config With respect to Azure AD authentication, I only added some app settings key in here.
    – Jay
    Commented May 25 at 15:48
  • See if this link helps: stackoverflow.com/questions/31588552/… Commented May 29 at 19:47

1 Answer 1

0

Configure proxy settings in Web.config:

<configuration>
  <system.net>
    <defaultProxy>
      <proxy autoDetect="true" />
    </defaultProxy>
  </system.net>
</configuration>

Custom middleware for HTTP headers forwarding:

using System.Threading.Tasks;
using System.Web;

public class ForwardHeadersMiddleware : OwinMiddleware
{
    public ForwardHeadersMiddleware(OwinMiddleware next) : base(next) { }

    public override async Task Invoke(IOwinContext context)
    {
        var request = context.Request;
        var response = context.Response;

        // Forward the headers
        var xForwardedFor = request.Headers["X-Forwarded-For"];
        var xForwardedProto = request.Headers["X-Forwarded-Proto"];
        var xForwardedHost = request.Headers["X-Forwarded-Host"];

        if (!string.IsNullOrEmpty(xForwardedFor))
        {
            response.Headers.Set("X-Forwarded-For", xForwardedFor);
        }

        if (!string.IsNullOrEmpty(xForwardedProto))
        {
            response.Headers.Set("X-Forwarded-Proto", xForwardedProto);
        }

        if (!string.IsNullOrEmpty(xForwardedHost))
        {
            response.Headers.Set("X-Forwarded-Host", xForwardedHost);
        }

        await Next.Invoke(context);
    }
}

A complex approach (OwinForwardedHeaderMiddleware): https://github.com/thehoneymad/OwinForwardedHeaderMiddleware/tree/master

3
  • I will try this today & let you know the outcome
    – Jay
    Commented May 31 at 12:54
  • sorry for the late response. I was reassigned to project. I made few modifications. This is a flow - 1) I click on Login button 2) User is redirected to microsoft online 3) Upon successful authentication user is redirected back to the website but the code that displays user name & hide login button based on is user authenticated never executes. I still see login button. 4) If I click one more time on login button then no password is asked, user redirected back to the website but still not logged in.
    – Jay
    Commented Jul 9 at 16:32
  • I am adding the error about nonce cookie I see in the browse. I don't see this on local. I added code to set nonce cookie's samesite attribute to "none" but still I am facing the same error. I have edited the question to add this results.
    – Jay
    Commented Jul 9 at 16:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.