Search code examples
c#wcfwebformswcf-security

WCF Service inside aspnet webforms throwing 302


I have created a WCF service inside an existing ASPNET webforms site, I then proceeded to add FormsAuthentication to the aspnet site, added a section in the location tag to allow anonymous access to the .svc file, I can browse through the WSDL file no issue, but when I try to call the service, I get a 302, the service is setup to use basicAuth.

I tried added a HttpModule to intercept the service request and return an appropriate message but that doesnt work as well.

Here is the Webconfig inside the service folder.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpModules>
      <add name="AuthRedirectHandler" type="Test.Modules.AuthRedirectHandler, Test" />
    </httpModules>
    <authorization>
      <allow users="?"/>
    </authorization>
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="AuthRedirectHandler" type="Test.Modules.AuthRedirectHandler, Test" preCondition="managedHandler"/>
    </modules>
  </system.webServer>
</configuration>

The HttpModule, addded a few other events but none get hit

public class AuthRedirectHandler : IHttpModule
    {
        public void Dispose()
        {
            //throw new NotImplementedException(); -- do nothing here
        }

        public void Init(HttpApplication context)
        {
            context.EndRequest += new EventHandler(context_EndRequest);
            context.BeginRequest += Context_BeginRequest;
            context.AuthenticateRequest += Context_AuthenticateRequest;
            context.AuthorizeRequest += Context_AuthorizeRequest;
            context.PreRequestHandlerExecute += Context_PreRequestHandlerExecute;
            context.PostAuthorizeRequest += Context_PostAuthorizeRequest;
        }

        private void Context_PostAuthorizeRequest(object sender, EventArgs e)
        {
            int k = 0;
        }

        private void Context_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            int k = 0;
        }

        private void Context_AuthorizeRequest(object sender, EventArgs e)
        {
            int k = 0;
        }

        private void Context_AuthenticateRequest(object sender, EventArgs e)
        {
            int k = 0;
        }

        private void Context_BeginRequest(object sender, EventArgs e)
        {
            int k = 0;
        }

        void context_EndRequest(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication) sender;

            if (app != null &&
                app.Response.StatusCode == 302)//302 Found
            {
                app.Response.ClearHeaders();
                app.Response.ClearContent();
                app.Response.StatusCode = 401;
            }
        }

When I check under fiddler request, I can do a normal HttpWebRequest to the service, but when I try to call a method, I get a 302 response that the proceeds to load my login page.


Solution

  • This is for some that might need reference on how to fix the issue, I ended up going down this route

    1. Remove any form of security to the .svc file
    2. Create a messageInspector to add Basic Auth header to WCF(Client)
    3. Add the messageInspector to a ServiceBehavior
    4. Add the serviceBehavior to your service endpoint behaviors
    5. In the Service, create a ServiceAuthorizationManager
    6. Add the ServiceAuthorizationManager to the web.config of your service

    1.Remove any security

    <location path="Services/UpdaterService.svc">
        <system.web>
          <authorization>
            <allow users="?"/>
          </authorization>
        </system.web>
      </location>
    

    2.Create a messageInspector to add Basic Auth header to WCF(Client)

    public class ServiceMessageServiceCredentialsInspector : IClientMessageInspector
        {
            public void AfterReceiveReply(ref Message reply, object correlationState)
            {
            }
    
            public object BeforeSendRequest(ref Message request, IClientChannel channel)
            {
    
                HttpRequestMessageProperty requestMessageProperty = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
                requestMessageProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
                        Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
    
                return null;
            }
        }
    

    3. Add the messageInspector to a ServiceBehavior

    public class ServiceInterceptionBehavior : BehaviorExtensionElement,IEndpointBehavior
        {
            public override System.Type BehaviorType
            {
                get { return typeof(ServiceInterceptionBehavior); }
            }
    
            public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
            {
    
            }
    
            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
                clientRuntime.MessageInspectors.Add(new ServiceMessageServiceCredentialsInspector());
            }
    
            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
            {
    
            }
    
            public void Validate(ServiceEndpoint endpoint)
            {
            }
    
            protected override object CreateBehavior()
            {
                throw new NotImplementedException();
            }
        }
    

    4. Add the serviceBehavior to your service endpoint behaviors

    EndpointAddress address = new 
          EndpointAddress("http://localhost:14138/Services/Service.svc");
                            ChannelFactory<IService> myChannelFactory = new 
          ChannelFactory<IUpdaterService>(defaultBinding, address);
                                myChannelFactory.Endpoint.EndpointBehaviors.Add(new ServiceInterceptionBehavior());
                                var address2 = myChannelFactory.CreateChannel(address);
    

    5. In the Service, create a ServiceAuthorizationManager

    public class ServiceAuthorizationManager : ServiceAuthorizationManager
        {
            protected override bool CheckAccessCore(OperationContext operationContext)
            {
                //Extract the Athorizationm Header,a nd parse out the credentials converting to base64 string
                var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
                if ((authHeader != null) && (authHeader != string.Empty))
                {
                    var svcCredentials = System.Text.ASCIIEncoding.ASCII
                       .GetString(Convert.FromBase64String(authHeader.Substring(6)))
                       .Split(':');
                    return DefaultPasswordValidator.ValidateCridentials(svcCredentials[0], svcCredentials[1]);
                }
                else
                {
                    //No authorization header was provided, so challenge the client to provide before proceeding:
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"UpdaterService\"");
                    //Throw an exception with the associated HTTP status code equivalent to HTTP status 401
                    throw new FaultException("Please provide a username and password");
                }
            }
    

    6. Add the ServiceAuthorizationManager to the web.config of your service

    <serviceAuthorization serviceAuthorizationManagerType="ServiceAuthorizationManager, AssemblyName, Version=2.0.0.1, Culture=neutral, PublicKeyToken=null" />
              <serviceAuthenticationManager serviceAuthenticationManagerType="ServiceAuthenticationManager, AssemblyName"
                authenticationSchemes="Basic" />