OWIN cookies SignIn error with AD FS

Problem Statement:

Claims aware web application logs in using ADFS for the first time successfully and then fails for all subsequent times when opened with new browser. The Web app is hosted on IIS and makes use of session variables.

 

Setting up a repro:

Creating the claims aware web application:

1. Open Visual Studio 2015 in administrator mode.

2. File -> New -> Project -> Visual C# - Web -> ASP.NET Web Application

1

 

3. On the “Select a template page” -> select “MVC” and then click on the button – “Change authentication”

2

 

4.  Here select “Work and school accounts” and then “on-premise” from the drop down if you have the ADFS set up in a server as I do.

5. Provide the ADFS metadata URL and the client app URI as it would be hosted on IIS.

6. Then click OK and again OK to complete the set up.

3

 

7. Once the application is created it would the following entries in Config file:

 <appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="ida:ADFSMetadata" value="https://Server01.contoso.com/FederationMetadata/2007-06/FederationMetadata.xml" />
<add key="ida:Wtrealm" value="https://client01.contoso.com/ReproWebApp2/" />
</appSettings>

 

8. Complete the hosting on IIS with the same URI as given previously–

4

 

9. Let us now create a random session variable to store a random value in it. For example, I used the following:

 public ActionResult Index()
{
Session["ABC"] = "Logged in";
return View();
}

 

10. Build the project and make sure there are no errors.

 

Setting up the ADFS for the new client application

1. On the ADFS server -> Open ADFS 2.0 Management

2. Expand “trust relationships”.

3. Right click on “Relying party trusts” to add a new one.

5

 

4. Select “Enter data about relying party manually”

6

 

5. Provide any display name and optional Notes.

6. Select AD FS 2.0 Profile.

7

 

7. For configure certificate – if you have a cert you can select that here – else click next.

8. In configure URL – Provide the client application URL

8

 

9. Click next for Configure Identifiers.

10. Select “Permit all users to access this relying party”.

9

 

11. Verify the entries on summary page and select Next.

12. Enable the checkbox “Open the Edit claim rules dialog for this relying party trust when the wizard closes” finish the set up.

13. Next, “Add Rule” -> Select from dropdown -> Send LDAP Attributes as claims”

10

 

14. Set the “Attribute Store” as Active directory and set a few claims as needed.

11

 

15. Once done, select “Finish” and “Apply”.

 

Executing the client app

1. Now back to the MVC web app -> try to execute the application, you may run into the following error : "The remote certificate is invalid according to the validation procedure”. 12

 

2. To quickly get around this issue set the ServerCertificateValidationCallback as follows, in the global.asaxapplication_start()

 ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{ return true; };

 

3. Now build and execute the Web App once again. This time it should prompt you for the credentials:

13

 

4. Enter valid credentials and you will be logged in successfully into your application.

14

 

5. Copy the URL and close the browser.

6. Open a new browser -> paste the URL OR execute the web app once again.

7. Enter the same credentials in the prompt and this time the log in fails:

15

 

 

Description of the issue: Sometimes it is necessary for the developer to make use of session variables in the application and this interferes with the cookies in Microsoft.Owin.Host.SystemWeb, i.e. Response.Cookies collection will overwrite any cookies set via the OWIN response headers. This causes the subsequent log-ins to fail.

 

Resolution: There is a workaround to fix the issue. The objective of the workaround is to not use the default cookie manager here, but create a new custom one – SystemWebCookieManager.

This is then brought into use by the below code in ConfigureAuth() in the file Startup.auth.cs

 app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebCookieManager()
});

The official URL for the workaround is - https://katanaproject.codeplex.com/wikipage?title=System.Web%20response%20cookie%20integration%20issues&referringTitle=Documentation

The implementation of the class is as follows:

 public class SystemWebCookieManager : ICookieManager
{
 public string GetRequestCookie(IOwinContext context, string key)
 {
  if (context == null)
  {
   throw new ArgumentNullException("context");
  }

   var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
   var cookie = webContext.Request.Cookies[key];
   return cookie == null ? null : cookie.Value;
 }

 public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
 {
  if (context == null)
  {
  throw new ArgumentNullException("context");
  }
  if (options == null)
  {
  throw new ArgumentNullException("options");
  } 

  var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
  bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
  bool pathHasValue = !string.IsNullOrEmpty(options.Path);
  bool expiresHasValue = options.Expires.HasValue;
  var cookie = new HttpCookie(key, value);

  if (domainHasValue)
  {
  cookie.Domain = options.Domain;
  }
  if (pathHasValue)
  {
  cookie.Path = options.Path;
  }
  if (expiresHasValue)
  {
  cookie.Expires = options.Expires.Value;
  }
  if (options.Secure)
  {
  cookie.Secure = true;
  }
  if (options.HttpOnly)
  {
  cookie.HttpOnly = true;
  }

  webContext.Response.AppendCookie(cookie);
}

 public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
 {
  if (context == null)
  {
  throw new ArgumentNullException("context");
  }
  if (options == null)
  {
  throw new ArgumentNullException("options");
  }

  AppendResponseCookie(
  context,
  key,
  string.Empty,
  new CookieOptions
  {
   Path = options.Path,
   Domain = options.Domain,
   Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
  });
 }
}

 

Now the log-ins will work every time.

 

Hope this helps!

 

Created by : Saurav Dey(MSFT)