WCF: SOAP/REST + SSL + Basic authentification + IIS

There are enough articles about working with SSL and Basic auth that usually describe decision for one protocol – either SOAP or REST – I try to cover both ones.

You can download my project by link.

IIS + SSL

See good explanation in article ‘How to Set Up SSL on IIS 7 -> IIS Manager‘.

REST + SSL + Basic auth

There’re some methods to resolve this task:

  1. Using RequestInterceptor from WCF REST Starter Kit (see article ‘Basic Authentication on a WCF REST Service‘ by Patrick Kalkman)
  2. You can try to use custom http-headers (see article ‘WCF REST client using custom http headers‘ by Kenneth Thorman)
  3. .. may be something else..
  4. I prefer to use ServiceAuthorizationManager-class to implement authorization with WCF.

Let’s I describe sample-project in a nutshell..

The backbone is class BasicAuthenticationManager which responsible for parsing BasicAuth http-header and checking of passed credentials (see comments in code for detailed explanation):

public class BasicAuthenticationManager : ServiceAuthorizationManager
    {
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            try
            {
                var msg = operationContext.RequestContext.RequestMessage;

                // If user requests standart help-page then ignore authentication check.
                if (msg.Properties.ContainsKey("HttpOperationName") && msg.Properties["HttpOperationName"].ToString() == "HelpPageInvoke")
                {
                    return base.CheckAccessCore(operationContext);
                }

                var httpRequestHeaders = ((HttpRequestMessageProperty) msg.Properties[HttpRequestMessageProperty.Name]).Headers;

                // Is Authorization-header contained in http-headers?
                if (!httpRequestHeaders.AllKeys.Contains(HttpRequestHeader.Authorization.ToString()))
                {
                    return false;
                }

                // Try to parse standart Basic-auth header.
                var authenticationHeaderBase64Value = httpRequestHeaders[HttpRequestHeader.Authorization.ToString()];
                var basicAuthenticationFormatString = Base64EncodeHelper.DecodeUtf8From64(authenticationHeaderBase64Value).Remove(0, "Basic ".Length);
                var basicAuthenticationParams = basicAuthenticationFormatString.Split(new[] {':'}, 2);
                var login = basicAuthenticationParams.FirstOrDefault();
                var password = basicAuthenticationParams.LastOrDefault();

                // Check credentials.
                // ! The simplest-hard implementation for demo.
                if (login != "user" || password != "password")
                {
                    return false;
                }
            }
            catch (Exception e)
            {
                return false;
            }

            return base.CheckAccessCore(operationContext);
        }
    }

Set the BasicAuthenticationManager in behavior-section of server side config-file:

<?xml version="1.0"?>

<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.0"></compilation>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <behaviors>
      <endpointBehaviors>
        <behavior name="">
          <webHttp helpEnabled="true" faultExceptionEnabled="true" />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceAuthorization serviceAuthorizationManagerType="WcfRestService.Infrastructure.BasicAuthenticationManager, WcfRestService" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceMetadata httpsGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <webHttpBinding>
        <binding name="">
          <security mode="Transport" />
        </binding>
      </webHttpBinding>
    </bindings>
  </system.serviceModel>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="UrlRoutingModule"
           type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

    </modules>
    <handlers>
      <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd"
           type="System.Web.HttpForbiddenHandler, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </handlers>
  </system.webServer>
</configuration>

Look at client side code.. Draw attention to adding basic auth http-header by hand.

private static void Main(string[] args)
        {
            try
            {
                var request = WebRequest.Create(string.Format("https://localhost:444/WcfRestSslBasicAuth/GetCurrentDateTime/{0}", Uri.EscapeDataString("rest-client (ssl-basic auth)")));

                // ! Remove this string in production code. Emulate working with the trusted certificate.
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // The straightforward passing  of credential parameter for demo.
                const string login = "user";
                const string password = "password";

                request.Headers.Add(
                    HttpRequestHeader.Authorization,
                    Base64EncodeHelper.EncodeUtf8To64(string.Format("Basic {0}:{1}", login, password)));

                using (var reader = new StreamReader(request.GetResponse().GetResponseStream()))
                {
                    Console.WriteLine(reader.ReadToEnd());
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Console.WriteLine("\n press Enter to exit..");
            Console.ReadLine();
        }

I won’t explain the way of hosting REST-service on IIS – please see good article ‘Hosting a WCF REST service on IIS (en-US)‘.

SOAP + SSL + Basic auth

When implementing this project i encountered only with problem of correct ssl-settings for both side. Luckily ozkar garcia resolved this issue – see “The HTTP request is unauthorized with client authentication scheme ‘Basic’” and following his decision we should setup server-binding as follows (see binding-section):

<?xml version="1.0"?>

<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.0"></compilation>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpsGetEnabled="true" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
                                    customUserNamePasswordValidatorType="WcfSoapService.Infrastructure.BasicAuthValidator, WcfSoapService" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="ServiceSoapBindingConfig">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service name="WcfSoapService.Services.Service">
        <endpoint binding="basicHttpBinding" bindingConfiguration="ServiceSoapBindingConfig"
                  contract="WcfSoapService.Interfaces.IService" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Next to define password validator based on UserNamePasswordValidator:

    public class BasicAuthValidator : UserNamePasswordValidator
    {
        #region Overrides of UserNamePasswordValidator

        public override void Validate(string userName, string password)
        {
            // The simplest-hard implementation for demo.
            if (userName != "user" || password != "password")
            {
                throw new SecurityTokenException("Validation Failed!");
            }
        }

        #endregion
    }

Don’t forget to add userNameAuthentication-section to web.config to apply custom password validator.

So client side is looked like this:

        private static void Main(string[] args)
        {
            try
            {
                using (var client = new ServiceClient())
                {
                    // The straightforward passing  of credential parameter for demo.
                    client.ClientCredentials.UserName.UserName = "user";
                    client.ClientCredentials.UserName.Password = "password";

                    // ! Remove this string in production code. Emulate working with the trusted certificate.
                    ServicePointManager.ServerCertificateValidationCallback += delegate { return true; };

                    Console.WriteLine(client.GetCurrentDateTime("soap-client (ssl-basic auth)"));
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Console.WriteLine("\n press Enter to exit..");
            Console.ReadLine();
        }

PS I recommend to use really convenient tool ‘WCF Service Configuration Editor’ for configuring services-sections in config-files (VS -> Tools -> WCF Service Configuration Editor).

About these ads

About vladimir77

Senior .NET developer.
This entry was posted in IIS, WCF. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s