EPiServer and Windows Identity Foundation (WIF)

1.1k views Asked by At

I am researching the possibilities of using WIF to identify users on parts of a customer's site running on EPiServer. I've managed to get WIF to kick in using, amongst others, the following post:

http://world.episerver.com/Blogs/Ben-Morris/Dates/2010/6/Converting-EPiServer-6-to-use-claims-based-authentication-with-WIF/

This works nicely if you set

<authorization>
    <deny users="?"/>
</authorization>

in web.config, making all requests require an authenticated user. However, we would like to use EPiServer to separate what content should be available to anonymous users and authenticated users. The problem is, I just can't get it to work.

When I enable WIF, and don't set deny users="*", EPiServer kicks in and outputs some text to the response stream before WIF is enable to perform the redirect:

HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Tue, 01 Nov 2011 07:51:04 GMT
Connection: close

Access denied.

</pre></table></table></table></table></table></font></font></font></font></font></i></i></i></i></i></b></b></b></b></b></u></u></u></u></u><p>&nbsp;</p><hr>

This results in the following error when WIF tries to redirect to the STS:

Server Error in '/' Application.

Cannot redirect after HTTP headers have been sent.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Web.HttpException: Cannot redirect after HTTP headers have been sent.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[HttpException (0x80004005): Cannot redirect after HTTP headers have been sent.] System.Web.HttpResponse.Redirect(String url, Boolean endResponse) +8712587
Microsoft.IdentityModel.Web.WSFederationAuthenticationModule.RedirectToIdentityProvider(String uniqueId, String returnUrl, Boolean persist) +249
Microsoft.IdentityModel.Web.WSFederationAuthenticationModule.OnEndRequest(Object sender, EventArgs args) +438
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +68 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75

I have searched both high and low to be able to override this behavior. In EPiServer.dll, I found the following place which outputs text similar to what is output:

AccessDeniedDelegateHandler.cs, method BrowserLogonAccessDenied(object sender):

internal static void BrowserLogonAccessDenied(object sender)
{
  HttpContext.Current.Response.Clear();
  HttpContext.Current.Response.Status = "401 Unauthorized";
  HttpContext.Current.Response.Write("Access denied.");
  HttpContext.Current.Response.Flush();
  HttpContext.Current.Response.End();
}

This code is called from the following two places, as far as I can see:

  • EPiServer.Global, method protected virtual void HandleAccessDenied()
  • EPiServer.PageBase, method public virtual void AccessDenied()

I have tried to override HandleAccessDenied in Global.asax, and override AccessDenied in my page template. However, the "Access denied" text is still output. It looks as if the override of AcccessDenied in my page template is firing, however, the override of HandleAccessDenied does not seem to fire.

Any hints on what could be wrong here?

2

There are 2 answers

0
Erik A. Brandstadmoen On BEST ANSWER

Found the problem. I tried overriding the AccessDenied method of PageBase, however, I did the same "mistake" as the default implementation, namely to flush the response stream:

    public override void AccessDenied()
    {
        Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        Response.Flush(); // <- This was the problem
        Response.End();
        // The rest is handled by WIF when we send a HTTP 401, think nothing more of it..
    }

The solution was to simply avoid flushing the response stream. Then WIF handles the rest:

    public override void AccessDenied()
    {
        Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        // Removed the flushing of the response
        Response.End();
        // The rest is handled by WIF when we send a HTTP 401, think nothing more of it..
    }

This allows us to use the authorization of EPI to control which users had access to each page.

1
Martin Ottosen On

Instead of overriding the AccessDenied behaviour, you can add a special login form (since you are still relying on forms authentication) to actually trigger the login.

public class FederatedLoginHandler : IHttpHandler, IRequiresSessionState
{
    public void ProcessRequest(HttpContext context)
    {
        if (context.User.Identity.IsAuthenticated)
        {
            FormsAuthentication.RedirectFromLoginPage(context.User.Identity.Name, false);
        }
        else
        {
            context.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
        }
    }
}

Setup the handler in web.config with

<system.webServer>
  <handlers>
    <!--"Fake" login handler, simply triggers WIF auth-->
    <add name="LoginForm" path="federatedlogin.ashx" verb="GET,HEAD" type="SomeAssembly.FederatedLoginHandler" />
    ...

And finally setup authentication to use the new handler

<authentication mode="Forms">
  <forms name=".LoginPage" loginUrl="federatedlogin.ashx" timeout="120" />
</authentication>

This strategy does not require modifying EPiServer, WIF or standard ASP.Net behaviour. Of course, you need the basic WIF configuration to get this to work http://msdn.microsoft.com/en-us/library/gg638734.aspx.