Archive for May, 2010

DNN ADFS Provider update

May 20, 2010 1 comment

It appears I was a little too hasty in my last blog. I got the authentication to work, but I started getting all kinds of Javascript errors like ‘sys is undefined’. Once I went to a page with my AJAX enabled controls the errors really started going nuts.

After much angst and looking at the HTTPFox traffic, I noticed that the requests for the AJAX resources ScriptResource.axd and WebResource.axd were getting redirected to the ADFS login page while the rest of the requests were getting through fine. Why would just the axd requests be redirected?

Upon further examination of the requests themselves the culprit seems to be cookies. The requests did send the DNN forms authentication ticket. The requests did not send the ADFS SAML cookies back with them, so ADFS was redirecting them back to the login screen.

If anyone knows why this happens or how to get the requests to return the proper cookies with it, I would love to know. Please leave a comment below.

So, how do you get past this? Well Joe Kaplan, in this post suggests a solution that works. I had to modify it slightly in order to make it work with DNN. Each of these will be discussed in depth later in the blog.

  • Create a virtual directory below the root.
  • Set up the child directory web.config
  • Add a web page to the child directory to handle the ADFS cookies
  • Modify the HTTPModule created in the previous blog

The following is what we are trying to achieve. The request will look something like this:

  • Request comes into the parent directory then redirect them to the child directory.
  • The ADFS Web Agent will redirect to wherever it is configured to go and when it is done it will redirect back to the child directory
  • On the default.aspx page of the child directory write the forms authentication ticket for the parent directory and redirect back to the parent
  • When the request comes back into the parent it will be authenticated and you can check within the same HTTPModule to see if the user is already created and officially log them onto your site (get the roles and permissions)

Set up the child directory web.config

Once you have created the child virtual directory you will need to set up the web.config. Specifically we need to turn off a lot of the functionality that DNN creates for us. If you don’t, the server will complain about not having the DNN DLLs in the child directories. Here is a sample web config. You will need to change the ADFS items to match your environment.

<?xml version="1.0" encoding="utf-8" ?>
		<sectionGroup name="system.web">
			<section name="websso"
         System.Web.Security.SingleSignOn, Version=,
         PublicKeyToken=31bf3856ad364e35, Custom=null" />

		<sessionState mode="Off" />
		<compilation defaultLanguage="c#" debug="true">
				<add assembly="System.Windows.Forms, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
				<add assembly="System.Design, Version=, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
				<add assembly="System.DirectoryServices, Version=, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
				<add assembly="System.Web.Security.SingleSignOn, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"/>
				<add assembly="System.Web.Security.SingleSignOn.ClaimTransforms, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"/>

				<!-- You will need to remove any other assemblies you have added in the parent -->				

		<customErrors mode="Off"/>
		<authentication mode="None" />

			<remove name="HTTP_Module_Test" />
			<add name="Identity Federation Services Application Authentication Module" type="System.Web.Security.SingleSignOn.WebSsoAuthenticationModule, System.Web.Security.SingleSignOn, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null" />
			<remove name="Compression" />
			<remove name="RequestFilter" />
			<remove name="UrlRewrite"  />
			<remove  name="Exception"  />
			<remove  name="UsersOnline"  />
			<remove  name="DNNMembership"  />
			<remove  name="Personalization"  />
			<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
			<remove  name="Analytics" />
			<!-- This is for CAPTCHA support -->
			<remove verb="*" path="*.captcha.aspx" />
			<!-- This is for Serving files, secure, insecure, from database -->
			<remove verb="*" path="LinkClick.aspx" />
			<!-- This adds syndication support -->
			<remove verb="*" path="RSS.aspx"  />
			<!-- This adds legacy support for the Logoff page -->
			<remove verb="*" path="Logoff.aspx"  />
			<!-- ASP.NET AJAX support -->
			<remove verb="*" path="*.asmx" />
			<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
			<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
			<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="true" />
		<pages validateRequest="false" enableViewStateMac="true" enableEventValidation="false">
				<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
				<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
				<add namespace="System.ComponentModel" />
				<add namespace="System.Data" />
				<add namespace="System.Data.SqlClient" />
				<add namespace="System.Drawing" />
				<add namespace="Microsoft.VisualBasic" />
				<add namespace="System.Globalization" />
				<remove namespace="DotNetNuke.Services.Localization" />
				<remove namespace="DotNetNuke.Entities.Users" />
				<remove namespace="DotNetNuke" />
				<remove namespace="DotNetNuke.Common" />
				<remove namespace="DotNetNuke.Data" />
				<remove namespace="DotNetNuke.Framework" />
				<remove namespace="DotNetNuke.Modules" />
				<remove namespace="DotNetNuke.Security" />
				<remove namespace="DotNetNuke.Services" />
				<remove namespace="DotNetNuke.UI" />
				<remove namespace="DotNetNuke.Entities.Portals" />
				<remove namespace="DotNetNuke.Common.Utilities" />
				<remove namespace="DotNetNuke.Services.Exceptions" />
				<remove namespace="DotNetNuke.Entities.Tabs" />
			<authenticationrequired />
			<!-- The final redirect for the application. -->
			<cookies writecookies="true">
			<!-- The Federation Server that hosts the application. -->
			<add name="WebSsoDebugLevel" value="255" />

		<trace autoflush="true" indentsize="3">
				<clear />
				<add name="LSLogListener"
        Listener, System.Web.Security.SingleSignOn, Version=, 
        PublicKeyToken=31bf3856ad364e35, Custom=null"
				  initializeData="c:\logdir\AuthLog.log" />

Add default web page to the child directory

After setting up the web.config and verifying that it redirects to ADFS properly you can then add the default page functionality. We will grab the identity and then create the forms authentication ticket for the parent directory and then redirect them back to the parent. I do not get any claims from my partner, just the identity, but this would also be where you could grab those claims and put them into a cookie.

protected void Page_Load(object sender, EventArgs e)
	bool CreatePersistentCookie = true;
	    SSO.SingleSignOnIdentity id = (SSO.SingleSignOnIdentity)User.Identity;
	    // set the forms authentication cookie ( log the user in )
	    FormsAuthentication.SetAuthCookie(id.Name, CreatePersistentCookie);
	    lbl.Text = id.Name;
	catch (Exception ex)
	    lbl.Text = ex.Message;

Modify the HTTPModule

Really you only have to add two lines of code to the beginning. We want to check and see if the request is authenticated. If it is not, then redirect to a url specified in the web.config. The rest will work as expected.

public void context_AuthenticateRequest(object sender, EventArgs e)
    HttpApplication app = (HttpApplication)sender;
    //Check to see if we are authenticated.  If not, then send the request to the
    //virtual directory that authorizes us
    if (!app.Request.IsAuthenticated)
	string url = ConfigurationManager.AppSettings["AuthorizationURL"];
	app.Response.Redirect(url, true);

For thoroughness, here is the change to the web.config.

	  <add key="AuthorizationURL" value="" />

That’s it! Hope this helps someone.