Archive for the ‘DNN’ Category

I Was Wrong About Being Wrong

June 9, 2010 7 comments

So, I am still trying to tackle the problem of how to make DNN integrate with ADFS without having to write a whole new authentication system. I was able to get the ADFS redirection and authentication to work, but the ADFS authentication cookies were not being sent with the AJAX requests to the ScriptReference.AXD and WebReference.AXD. The requests were not sending the ADFS cookies with the requests and were being redirected to the authentication page.

The last solution of redirecting to a virtual directory worked but, I had a nagging feeling that this was not necessary. As it turns out, I was right. The original solution will work if you set your ADFS cookie path to “/”. The test I was running was in a virtual directory and so I set the cookie path to “/VirtualDirectory” thinking this was needed for the ADFS piece to work.

I modified my HTTP module to be configurable to run either scenario (redirected or not). I changed the ADFS cookie path and tested it and everything worked correctly.

Categories: ADFS, DNN, SiteMinder

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.

Integrate DNN with ADFS without writing a new Authentication Provider

April 28, 2010 3 comments

I recently went through the pain of trying to integrate ADFS with DNN. I hope to spare someone else the pain I went through.

My partner organization already had ADFS setup. My goal was to have the partner organization authenticate the user and then have DNN automatically recognize the user, not having to enter any more credentials. From there we wanted to manage the groups and permissions through the regular DNN portal.

I was initially under the misconception that I could not use the current DNNMembershipProvider and I would have to create a whole new Authentication Provider and collect the credentials and pass them to my partner organization.

What I have figured out is that you CAN use the existing DNNMembership provider, but you have to create the user before the request gets to the DNNMembership provider using the information provided by ADFS.

Note: This method is a straight pass-through from ADFS to DNN. No measures are taken to authenticate the user or to check any of the inputs from ADFS.

As an aside, this method works not only with ADFS but also any other external 3rd-party Authentication method that sends user information in headers or cookies. Change the context_AuthenticateRequest method to suit your requirements.

Here are the pre-conditions to this article. I expect:

  • Your partner organization has ADFS up and running
  • You have your ADFS proxy up and running and it talks to the partner organization.
  • ADFS Web Agent is installed on the server you are running.

Let’s get started.


What you have to do is create an HTTPModule that will intercept the AuthorizeRequest events before DNN does. An over-simplified definition of an HTTPModule is, it is a piece of code that runs before any page gets hit. You can listen for a lot of different events. For more information, click here.

To create the HTTPModule you will need to:

  • Open Visual Studio and create a new class library.
  • Create a new class and copy the code attached to this blog.
  • Add References to the following DLLs
    • DotNetNuke
    • System.Web
    • System.Web.Security.SingleSignOn
    • System.Web.Security.SingleSignOn.ClaimsTransform
    • System.Web.Security.SingleSignOn.Permissions
  • Compile the solution
  • The DLL that is created will need to be placed in the bin directory of your website.
  • Make changes to your web.config (explained in a later section).

I will walk through a bit of the code. Here is the first snippet of code. We start listening for the AuthenticateRequest event. All other events pass through untouched.

public void Init(HttpApplication context)
//Start listening for each authentication request
context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);

Now, what do we do when this event is fired off? To shorten the namespace I added the statement

using SSO = System.Web.Security.SingleSignOn;

First off, we need to get the information that ADFS has sent us by casting the User.Identity into the ADFS object SingleSignOnIdentity.

public void context_AuthenticateRequest(object sender, EventArgs e)
HttpApplication app = (HttpApplication)sender;
//By the time the request gets to here, it should have been authenticated
//against ADFS.
SSO.SingleSignOnIdentity id = (SSO.SingleSignOnIdentity)app.User.Identity;

At this point you will have access to the user’s Identity and any claims coming from the ADFS server. You can access them through id.SecurityPropertyCollection. You can use them to populate the new user account. You can iterate through the claims with the following code

foreach(SecurityProperty sp in id.SecurityPropertyCollection)

Next, we check to see if the use already exists in the database by using the DNN API function GetUserByName. If it doesn’t, then the user is created by the standard DNN API function CreateUser and logged in. If the user does exist already then we log them in automatically. The user will automatically be added to the Registered Users and Subscribers security groups.

//'See if user exists in DNN Portal user DB
UserInfo objUserInfo = UserController.GetUserByName(currentPortal.PortalId, id.Name);
//' user does exist - try to create on the fly
if (objUserInfo == null)
    objUserInfo = new UserInfo();
    objUserInfo.DisplayName = id.Name;
    objUserInfo.FirstName = id.Name;
    objUserInfo.LastName = id.Name;
    objUserInfo.Username = id.Name;
    objUserInfo.Membership.Password = "AReallyStrongPassword";
    objUserInfo.PortalID = currentPortal.PortalId;
    objUserInfo.Email = id.Name;
    UserCreateStatus objUserCreateStatus = UserController.CreateUser(ref objUserInfo);
    //See if the user was added successfully
    if (objUserCreateStatus == UserCreateStatus.Success)
        //We have created them successfully, so let them into the site
        //This will send the error to the error log, but the user will experience an infinite loop
        throw new Exception("User not created successfully: " + objUserInfo.Username + "- " +         objUserCreateStatus.ToString());

Here is the LetsLogUserIn function:

private void LetsLogUserIn(UserInfo objUserInfo)
    //Get the current portal
    PortalSettings currentPortal = PortalController.GetCurrentPortalSettings();
    //set the language to the default language of the portal
    //Log the user in
    catch(Exception ex)


We need to make several changes to the web.config. First we need to make the changes necessary for ADFS and then we need to make changes for our HTTPModule.

The ADFS changes are the standard web.config changes you would do for any ADFS claims-aware site. You first need to add the section groups to your web.config.

<sectionGroup name="system.web">
    <section name="websso" type="System.Web.Security.SingleSignOn.WebSsoConfigurationHandler,                                  System.Web.Security.SingleSignOn, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null" />

Then you need to add the actual section. The <returnurl> needs to be EXACTLY what is put into ADFS. Remember, this URL needs to have a / at the end to prevent ADFS from posting to a directory listing. The <fs> element needs to be changed to reflect the name of your server.

        <authenticationrequired />

If you would like to have logging (and who doesn’t like loggingJ) you will need to add the following section at the end of your web.config

       <!-- enables full debug logging -->
       <add name="WebSsoDebugLevel" value="255" /> 
     <trace autoflush="true" indentsize="3">
         <!-- either create a c:\logs directory and grant Network Service permission to write to it, or remove this listener -->
         <add name="MyListener"
              type="System.Web.Security.SingleSignOn.BoundedSizeLogFileTraceListener, System.Web.Security.SingleSignOn, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"
              initializeData="c:\logs\webagent.log" />

Finally, you need to add the HTTPModules for ADFS and our HTTPModule. Order matters a lot for the HTTPModules. We need the ADFS Web Agent to be listed first, so that any request will be redirected to our partner’s logon screen. Immediately following the Web Agent should be our HTTPModule. This will ensure that we will automatically log them on before DNN even see it.

    <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" />

    <add name="HTTP_Module_Test" type="HTTP_Module_Test.SSO_Module, HTTP_Module_Test, Version=, Culture=neutral, PublicKeyToken=null"/>

There you have it Hope it helps someone.