Creating OWIN based WS-Federation application


Introduction

Recently I have worked on an ASP.NET MVC application which uses OWIN middleware components for WS-Federation authentication. This post gives you an overview about OWIN and step-by-step guide to create basic WS-Federation application using OWIN middleware components.

What is OWIN?

Open Web Interface for .NET (OWIN) defines an abstraction between .NET web servers and web applications. OWIN is a community-owned specification, not an implementation. The Katana project is a set of open-source OWIN components developed by Microsoft.

OWIN divides an application into four logical layers.

  1. Host- manages the underlying process and orchestrates the request execution.

    Three primary hosting options as of now are (a)IIS (b)Custom Host (c)OwinHost.exe

  2. Server- opens a network socket, listens for requests, and sends them through the pipeline of OWIN components. These list of OWIN compoents (aka middleware) are specified in OWIN startup class. 

    OWIN server is also responsible for populating the environment dictionary - IDictionary<string, object>. Currently, Katana project includes two server implementations:

    1. Microsoft.Owin.Host.SystemWeb - Used by IIS

    2. Microsoft.Owin.Host.HttpListener - Used by Self-Host and OwinHost.exe

  3. Middleware- is an independent component sits in the pipeline and does its work. We as developers build these middleware components to do specific and focused tasks and plug them in the pipeline. These middleware components can be visualized as traditional HTTPModules.

    The security related HTTPModules have moved to be OWIN middleware. Hence the functionality provided by traditional Windows Identity Foundation - WIF (two HTTP Modules - WSFederationAuthenticationModule, SessionAuthenticationModule) modules is already available as OWIN middleware components. 

    And this blog post explains steps to create a basic OWIN based application which uses OWIN middleware components for WS-Federation authentication.

  4. Application - OWIN based application is no different from the any web application except the fact that, it gets the context about the request from the environment dictionary and registers list OWIN middleware components in Startup class.
To summarize, OWIN is an abstraction to decouple application programming models and frameworks from server and hosting infrastructure. The separation of middleware components from each other and from the underlying infrastructure components enables the components to evolve in their own pace.

Create simple ASP.NET MVC Application

Create an empty ASP.NET MVC project in Visual Studio

Install the following OWIN related Nuget packages in the project using the Package Manager Console. These packages are required for the WS-Federation authentication.

  1. Install-Package Microsoft.Owin.Host.SystemWeb
  2. Install-Package Microsoft.Owin.Security.WsFederation
  3. Install-Package Microsoft.Owin.Security.Cookies

The above commands installs the OWIN hosting infrastructure and WS-Federation related middleware components.

Add OWIN startup class by using Add -> OWIN Startup class item on the context menu of project in Visual Studio.

In the Startup class add the following method to register two middleware components in OWIN pipeline to handle WS-Federation protocol.

public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
}

public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
MetadataAddress = "https://login.microsoftonline.com/2c2a63d4-465c-464d-b7f5-6ccfd0dce5b8/federationmetadata/2007-06/federationmetadata.xml",
Wtrealm = "https://testService.local/",
});

app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
}
}

Note that the MetadataAddress and Wtrealm are the values specific to my application and the STS I used in this sample. These values needs to be modified according to the application.

The two middleware components are involved in the WS-Federation authentication pipeline as mentioned below:

  • WS-Federation authentication component - Handles the SAML tokens received from STS and redirects user to STS if not authenticated.
  • Cookie authentication component - Creates and drops a cookie (.AspNet.Cookies) on to users browser and manages the session between client browser and the relying party application.

Note that in the above code snippet, we are setting the default authentication type to CookieAuthentication. If it is not so, you will see an infinite (actually 6) redirects between your application and STS and finally STS throws error.

Now, add a controller to the MVC application and decorate it with [Authorize] attribute.

[Authorize]
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
ViewBag.ClaimsIdentity = System.Threading.Thread.CurrentPrincipal.Identity;
var claimsIdentity = System.Threading.Thread.CurrentPrincipal.Identity as ClaimsIdentity;
ViewBag.DisplayName = claimsIdentity.Claims.First(c => c.Type == ClaimTypes.GivenName).Value;
return View();
}

public ActionResult LogOff()
{
if (User.Identity.IsAuthenticated)
{
var owinContext = this.Request.GetOwinContext();
var authProperties = new AuthenticationProperties();
authProperties.RedirectUri = new Uri(this.HttpContext.Request.Url, new UrlHelper(this.ControllerContext.RequestContext).Action("PostLogOff")).AbsoluteUri;
owinContext.Authentication.SignOut(authProperties);
return View();
}
else
{
throw new InvalidOperationException("User is not authenticated");
}
}

[AllowAnonymous]
public ActionResult PostLogOff()
{
return View();
}
}

The above controller does not do much, just displays the list of claims received from the STS. Below CSHTML (View) actually displays the claims in a table format:

<h2>Welcome: @ViewBag.DisplayName</h2>
<h3>Values from Identity</h3>
<table>
<tr>
<th>
IsAuthenticated
</th>
<td>
@ViewBag.ClaimsIdentity.IsAuthenticated
</td>
</tr>
<tr>
<th>
Name
</th>
<td>
@ViewBag.ClaimsIdentity.Name
</td>
</tr>
</table>
<h3>Claims from ClaimsIdentity</h3>
<table border="1">
<tr>
<th>
Claim Type
</th>
<th>
Claim Value
</th>
<th>
Value Type
</th>
<th>
Subject Name
</th>
<th>
Issuer Name
</th>
</tr>
@foreach (System.Security.Claims.Claim claim in ViewBag.ClaimsIdentity.Claims)
{
<tr>
<td>
@claim.Type
</td>
<td>
@claim.Value
</td>
<td>
@claim.ValueType
</td>
<td>
@claim.Subject.Name
</td>
<td>
@claim.Issuer
</td>
</tr>
}
</table>

Setup Relying Party in AD FS

I used Azure Active Directory (AAD) as the Security Token Service (STS) and registered my sample MVC application as relying party. You can use any available STS.

While registering the relying party application be careful about the relying party URL/identifier. In my case I have used application URI (https://testService.local/) as both URL and Identifier. You can also get the federation metadata URL from the STS. In case AAD, it is listed in the Metadata section of relying party application.

We use this federation metadata URL of STS as the MetadataAddress in the Startup class of MVC application.

Running the application

I run the application by hosting it in my local IIS but that is just a personal preference. You can run it by pressing F5 in visual studio. However, based on this we need to update the Relying party application URL in STS and also in the Startup class of sample MVC application.

When you run the application, browser sends request to /Home/Index action of our MVC application. Since this action method is decorated with [Authorize] attribute, and since user is not authenticated yet, application throws 403 Forbidden error.

This 403 Forbidden error is caught by OWIN middleware component, and turns it into 302 Redirect to STS application by reading the Federation Metadata XML which we have specified in the Startup class.

Now user is redirected to STS website and user provides correct credentials to login to STS. Once STS validates the credentials, it constructs and sends the SAML token to the application via HTTP POST (this is done by auto-submitted <form>).

This POST request to our application along with SAML Token is again caught by OWIN middleware component. OWIN component now reads the SAML token constructs a session cookie (.AspNet.Cookies) out of it. This cookie is sent back to the browser establishing a session between our application and client browser.

Hence subsequent request to protected page (like /Home/Index) does not need to go through entire cycle. Whenever the request from client includes a valid session cookie, application decodes it and constructs the Identity out of it.

Sign out from the application

To sign out from the application, I have used one more action method in the Home controller called - LogOff(). This method invokes the SignOut() method of OWIN authentication manager with an instance of AuthenticationProperties class. I have set a property - RedirectUri of authentication properties to redirect user back to our application page after sign out.

var owinContext = this.Request.GetOwinContext();

var authProperties = newAuthenticationProperties();

authProperties.RedirectUri = newUri(this.HttpContext.Request.Url, newUrlHelper(this.ControllerContext.RequestContext).Action("PostLogOff")).AbsoluteUri;

owinContext.Authentication.SignOut(authProperties);

 

The  above code snippet is used to redirect user back to our application after sign out.

I hope this blog post helps you understand about OWIN and implement WS-Federation authentication using OWIN in an MVC application. Attached the source code of my sample project.

Solution is now available in Git at the following location - GitHub

SampleOWINApp.zip

Comments (2)

  1. Sam says:

    Great starter article! One follow-up question:
    Can I use tokens instead of cookies in this sample? If yes, can you please point me to some pointers.

    Thanks

  2. kevin says:

    Following the directions above I get the error, "JavaScript is required. This web browser does not support JavaScript or JavaScript in this web browser is not enabled." My browser is setup to allow Javascript. This happens after the 302. I believe its the login page that is throwing the error.

Skip to main content