Contact Us
Magenic
  • What We Do
  • How We Do It
  • Our Thinking
  • Join Us
  • Our Work
  • Industries
  • Cloud
  • Software Development
  • Quality Engineering
  • DevOps
  • Strategy
  • Experience Design
  • Data & Integration
  • Advisory Services
  • Careers
  • Culture
  • Events
  • Success Stories
  • Partnerships
  • Technologies
  • Professional Services
  • Financial Services
  • Retail
  • Health Care

Mixed Mode ADFS 2.0 and Forms Authentication in a Single ASP.NET Web Application

April 17, 2015 // By Magenic

One project I worked on was for a custom Software as a Service (SaaS) application that needed to support federated log-in for some tenants, but standard forms authentication for others. After extensive research (aka ya-goo-bing) , it always looks like ASP.NET applications can only support one authentication mechanism at a time. This seemed stupid to me (and in particular because I was working on a SaaS app that requires something else) so I looked for ways to get around this. Its probably important to note that this isn’t the only reason you might want to support ADFS along with another security mechanism – other reasons might include:

  • Supporting single sign-on users from within the corporate network across both intranet and cloud applications without having to re-enter credentials
  • Perhaps your IT department doesn’t allow you or want you to add temporary contract workers to AD but yet they need access to an application
  • And, of course multi-tenant SaaS

First the use case – in this case, a multi-tenant SaaS application wants to allows some customers (tenants) to authenticate using ‘normal’ forms authentication. Other customer wish to federate their active directory using ADFS (or another WS-Federation compliant service that works with ADFS). Anyway, the problem is two fold – how to pre-identify the tenant so we know whether to send them down the ADFS route or the Forms Authentication route and secondly how to make the 2 authentication mechanisms play nice with one another.

My first attempt was with ADFS (v1 I think – the version that’s included with Windows Server 2008 R2) and that failed miserably – the http module included with that version (System.Web.Security.SingleSignOn.WebSsoAuthenticationModule) is sealed and thus wouldn’t allow me to override and inject the logic I needed to make a hybrid authentication solution work.

So, ADFS v2.0 was released and is based on Windows Identity Foundation. This looked more promising and I was able to build a solution on this. Here’s how I did it.

First I had to dig into how Forms Authentication does what it does – I think a sequence diagram for a request is in order here to show where it issues redirects – this is basically what forms authentication (alone) does to login…

Forms Sequence diagram

Figure 1: Forms Authentication Sequence

In the diagram above, the following takes place:

  • A request comes in and the URL auth module indicates that access isn’t allowed (via the in web.config under system.web/authorization (normal asp.net authentication).
  • in response, it sends a http 401 error -
  • the browser will redirect to the log-in page.
  • everything from this point is pretty common knowledge and there are plenty of articles out there on forms auth ;)

Now, let’s have a look at how ADFS does its thing when enabled by itself

ADFS authentication sequence

Figure 2: ADFS Authentication Sequence

OK, so this works basically the same as forms authentication except the redirect is to the ADFS server log-on service url (which will do the realm discovery and log-in stuff that ADFS does). This is basically step 1 in an ADFS Passive Requestor Profile (a WS-Federation piece that uses browser redirects to sign in with ADFS).

OK, so finally the proposed solution; the idea here is to override some of the functionality in the ADFS http module in EndRequest so we can determine what type of authentication to use. The sequence diagram will look like this:

Combination Authentication Sequence

Figure 3: Combination ADFS and Forms Authentication

OK, so to realize this, I had to implement a http module that derives from the WIF WSFederationAuthenticationModule – that code is below.

public class MyFederationModule : WSFederationAuthenticationModule
{
    /// <summary>
    /// Overrides the handling of the WIF federation module's end request 
    /// - basically if we receive
    /// a http 302 and the target redirect is Accont/Login.aspx then
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    protected override void OnEndRequest(object sender, EventArgs args)
    {
        HttpApplication app = (HttpApplication)sender;
        HttpRequest req = HttpContext.Current.Request;
        HttpResponse resp = app.Response;
        if (app.Response.StatusCode == 302 
		&& !string.IsNullOrEmpty(app.Response.RedirectLocation) 
		&& resp.RedirectLocation.ToLower().Contains("account/login.aspx"))
        {

            string customer = req.Cookies["customer"].Value;
            // first we need the customer - then we'll get redirected back here
            if (string.IsNullOrEmpty(customer))
            {
                HttpContext.Current.Response.Redirect(
		    "~/PromptForCustomer.aspx?redirect="+
		    app.Response.RedirectLocation, false);
                return;
            }
            bool isAdfs = GetCustomerSecuritySetting(customer);
            if (isAdfs)
            {
                // before we call base, set status code to 401 
		//which is what it expects when it needs to kick in
                app.Context.Response.StatusCode = 401; 
                base.OnEndRequest(sender, args);
            }
            else
                return; // forms authentication
        }
    }

    private bool GetCustomerSecuritySetting(string customer)
    {
        // TODO: lookup the customer by name and see if they are federated 
        if (customer.Equals("Federated"))
            return true;
        else
            return false;
    }
       
}

What happens here is pretty simple, on EndRequest we check to see if the http response code is 302 (redirect) and the redirect location is the forms auth login page – that’s our que to kick in and check what customer it is – the example just uses a cookie called customer that is set from a forms page called PromptForCustomer.aspx – the job of that page is to prompt for the customer (or however you will determine whether adfs is used or not) and set the cookie and then redirect back to the original location – that’ll cause the logic to run again. For that page to work it will have to allow anonymous access with a tag in web.config so the URL authorization module allows the redirect to go there.

Beyond that, you have to have forms authentication turned on and setup the web site with WIF STS Reference (a menu item on the project when you have WIF installed – it references the ADFS federation metadata).

That’s about it. Enjoy

This post was republished from Magenic Solutions Architect Dave Stienessen’s blog, The Pragmatic Architect, and can be found here.
If you would rather speak to us directly, please go to our contact page or call us at 877-277-1044.

Categories // Software Development
Tags ADFS, ASP.NET, SaaS WIF
SHARE:
THE LATEST:
  • FEBRUARY 23, 2021 // blog
    Stronger Product Owner = Better Software, Faster
  • FEBRUARY 19, 2021 // blog
    Security In Five Bi-Weekly Roundup – 2/19/21
  • FEBRUARY 18, 2021 // blog
    Doesn’t Everybody Know This?
Featured Content:
  • JANUARY 25, 2021 // White Paper
    The Top 7 Technology Trends of 2021

Related Posts

Blog
The Value of Code Reviews
Learn More
Blog
5 Things You Need To Know About Microsoft .NET 5
Learn More
Blog
Modernizing .NET Applications
Learn More
Blog
Mocking Injected Types in Blazor
Learn More

Ready to speak with our experts?

Have a question?

This field is required
This field is required
This field is required
This field is required

Thanks for Contacting Magenic

One of our experts will be contacting you directly within the next business day.

Return To Home
Magenic

info@magenic.com+1.877.277.1044

  • Facebook
  • Twitter
  • LinkedIn
  • YouTube
  • RSS Feed

© Magenic Inc.Privacy NoticeTerms & ConditionsSitemap