views:

1111

answers:

1

I am calling WCF from an ASP.NET page using ASP.NET Ajax. I am using Forms Authentication to secure the website.

Everything was working as expected through development until it was deployed onto the production server then I started getting javascript errors because the service could not be found. The production server is using SSL so I added the following to my web.config

<webHttpBinding>
    <binding name="webBinding">
    <security mode="Transport" />
    </binding>
</webHttpBinding>

This stopped the javascript errors from occurring but now the WCF service does not behave as it use to.

Before setting security to Transport a call to the WCF service from ASP.NET Ajax would execute the Application_AuthenticateRequest in my Global.asax. This would setup a custom IPrinciple on HttpContext.Current.User based on the Forms Authentication ticket. The constructor for my WCF service sets Thread.CurrentPrinciple = HttpContext.Current.User so my service has access to the IPrinciple set during Application_AuthenticateRequest.

After changing the security to Transport it does not appear to be running through Application_AuthenticateRequest because my Thread.CurrentPrinciple is not my custom IPrinciple.

Does anyone know how I can get the same behavior as before using Transport as after using Transport?

My webpage is using the following to reference the WCF service:

<asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="
    <Services>
        <asp:ServiceReference Path="~/Services/MyService.svc" />
    </Services>
</asp:ScriptManagerProxy>

Code used in my Application_AuthenticateRequest:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    String cookieName = FormsAuthentication.FormsCookieName;
    HttpCookie authCookie = Context.Request.Cookies[cookieName];

    if (authCookie == null)
    {
        return;
    }

    FormsAuthenticationTicket authTicket = null;
    try
    {
        authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    }
    catch
    {
        return;
    }

    if (authTicket == null)
    {
        return;
    }

    HttpContext.Current.User = new CustomPrinciple(authTicket.Name);
}
A: 

Simply changing the security from None to Transport shouldn't affect the application running through Authenticate_Request. You should probably attach the debugger to the service implementation and see what HttpContext.Current.User is. Is it your custom principal or is it also not set? You also may want to add a couple of Debug.Write messages in there and see what the order of operations is. Is the Authenticate_Request event happening after you're expecting it?

You may be running into this issue from MS Connect if you're using the <serviceAuthorization principalPermissionMode="UseAspNetRoles" /> in your config - it appears this setting isn't compatible with a secured transport and WCF overrides the principal. If you're setting the principal, it appears you need to use principalPermissionMode="None" on that.

Make sure you still have the ASP.NET Compatibility Mode enabled on the service either through config (<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />) or through attributes on the service ([AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]). Without that, the service won't have access to HttpContext.Current.

Also, rather than getting the HttpContext.Current.User in the service implementation constructor, try moving it to a behavior you attach to the service. Implement an IDispatchMessageInspector that handles the AfterReceiveRequest event and transfers the principal from the web context to the thread. It'll look something like this:

public class HttpContextPrincipalInspector : IDispatchMessageInspector
{
  public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  {
    if (HttpContext.Current != null)
    {
      IPrincipal principal = HttpContext.Current.User;
      Thread.CurrentPrincipal = principal;
    }
    return null;
  }

  public void BeforeSendReply(ref Message reply, object correlationState) { }
}

And, of course, implement an IEndpointBehavior to attach the dispatcher...

public class HttpContextPrincipalBehavior : IEndpointBehavior
{
  public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  {
    ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
    foreach (EndpointDispatcher endpointDispatch in channelDispatcher.Endpoints)
    {
      endpointDispatch.DispatchRuntime.MessageInspectors.Add(new HttpContextPrincipalInspector());
    }
  }

  // AddBindingParameters, ApplyClientBehavior, and Validate implementations
  // can be empty - they don't do anything.
}

...and a custom BehaviorExtensionElement so you can use it in WCF configuration:

public class HttpContextPrincipalElement : BehaviorExtensionElement
{
  public override Type BehaviorType
  {
    get { return typeof(HttpContextPrincipalBehavior); }
  }

  protected override object CreateBehavior()
  {
    return new HttpContextPrincipalBehavior();
  }
}

You can then configure any of your services to use this as an endpoint behavior so the service implementation isn't talking to the web context directly. Makes it a little easier to test and may solve the problem entirely - any time a message comes in for the service, it'll automatically transfer the principal over rather than relying on construction of the service.

Travis Illig

related questions