How do you pass user credentials from WebClient to a WCF REST service?
Asked Answered
R

3

8

I am trying to expose a WCT REST service and only users with valid username and password would be able to access it. The username and password are stored in a SQL database.

Here is the service contract:

public interface IDataService
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json)]
    byte[] GetData(double startTime, double endTime);
}

Here is the WCF configuration:

<bindings>
  <webHttpBinding>
    <binding name="SecureBinding">
      <security mode="Transport">
        <transport clientCredentialType="Basic"/>
      </security>
    </binding>
  </webHttpBinding>
</bindings>
<behaviors>
  <serviceBehaviors>
    <behavior name="DataServiceBehavior">
      <serviceMetadata httpGetEnabled="true"/>
      <serviceCredentials>
        <userNameAuthentication
           userNamePasswordValidationMode="Custom"
           customUserNamePasswordValidatorType=
                 "CustomValidator, WCFHost" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>
<services>
  <service behaviorConfiguration="DataServiceBehavior" name="DataService">
    <endpoint address="" binding="webHttpBinding"
              bindingConfiguration="SecureBinding" contract="IDataService" />
  </service>
</services>

I am accessing the service via the WebClient class within a Silverlight application. However, I have not been able to figure out how to pass the user credentials to the service. I tried various values for client.Credentials but none of them seems to trigger the code in my custom validator. I am getting the following error: The underlying connection was closed: An unexpected error occurred on a send.

Here is some sample code I have tried:

   WebClient client = new WebClient();
   client.Credentials = new NetworkCredential("name", "password", "domain");
   client.OpenReadCompleted += new OpenReadCompletedEventHandler(GetData);
   client.OpenReadAsync(new Uri(uriString));

If I set the security mode to None, the whole thing works. I also tried other clientCredentialType values and none of them worked. I also self-hosted the WCF service to eliminate the issues related to IIS trying to authenticate a user before the service gets a chance.

Any comment on what the underlying issues may be would be much appreciated. Thanks.

Update: Thanks to Mehmet's excellent suggestions. Here is the tracing configuration I had:

 <system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true">
        <listeners>
          <add name="xml" />
        </listeners>
      </source>
      <source name="System.IdentityModel" switchValue="Information, 
               ActivityTracing" propagateActivity="true">
        <listeners>
          <add name="xml" />
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="xml"
           type="System.Diagnostics.XmlWriterTraceListener"
           initializeData="c:\Traces.svclog" />
    </sharedListeners>
  </system.diagnostics>

But I did not see any message coming from my Silverlight client. As for https vs http, I used https as follows:

string baseAddress = "https://localhost:6600/";
_webServiceHost = new WebServiceHost(typeof(DataServices), 
                new Uri(baseAddress));
_webServiceHost.Open();

However, I did not configure any SSL certificate. Is this the problem?

Rianna answered 27/4, 2009 at 23:46 Comment(5)
Alex, you need to configure your service to use secure transport channel (https). The bindings on client and server should match. If you're hosting your service on IIS, you will also need to configure SSL for your site. You may also need to increase the verbosity in your tracing to get more details. Take a look at the options. Service Configuration Editor makes it easy to edit WCF settings.Rheinland
Mehmet, thanks. I "configured" my service to use https within the base address URI. For the WebClient instance, I specified the same address with "https" (i.e., localhost:6600). Are these incorrect? How would you configure them otherwise? As for the tracing, I set it to Verbose which is supposed to log everything. It did log more stuff but I did not see anything coming from the Silverlight client. Thanks.Rianna
How is WCF configured on the client side especially the bindings? Have you configured IIS and your service for SSL?Rheinland
Also try setting clientCredentials type to None. If you're hosting in IIS and basic authentication is enabled for the virtual directory for your service, IIS will handle the authentication and your custom user credentials validator will not be called. I have a web service built with WCF and that's how it is configured.Rheinland
Is it just me or does the WebClient class in Silverlight not even expose a Credentials property?Rab
G
2

Since you are using 'Basic' authentication you need to send the username and password in the request header. The example of manually adding the credentials to the header is displayed below:

HttpWebRequest req = (HttpWebRequest)WebRequest.Create(@"https://localhost:6600/MyWCFService/GetData");
//Add a header to the request that contains the credentials
//DO NOT HARDCODE IN PRODUCTION!! Pull credentials real-time from database or other store.
string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes("userName" + ":" + "password"));
req.Headers.Add("Authorization", "Basic " + svcCredentials);
//Parse the response and do something with it...
using (WebResponse svcResponse = (HttpWebResponse)req.GetResponse())
{
  using (StreamReader sr = new StreamReader(svcResponse.GetResponseStream()))
  {
    //Sample parses json response; your code here may be different
    JavaScriptSerializer js = new JavaScriptSerializer();
    string jsonTxt = sr.ReadToEnd();
  }
}
Garrulity answered 3/7, 2012 at 3:7 Comment(2)
how can i implement this with NTLM authentication ?Juna
Look at this: allen-conway-dotnet.blogspot.com/2010/01/… or this msdn.microsoft.com/en-us/library/ff648505.aspx for examples on doing Windows Style authentication with WCF.Garrulity
U
0

What atconway suggested above seems correct way but your service has to read base 64 string data from header and convert back to string and then authenticate. But it needs to authenticate on every call.

One more approach is to use secure key. and token

  1. Every client has secure key which he sends on first request , probably in header.
  2. You can generate key like key = MD5Hash(username+password);
  3. In response he gets token the , the token is then sent in each request. Token can be Guid. Every token expired after x mins.
  4. On server side you can maintain a singleton dictionary like Dictionay , to check token expiration when current time > expiration time remove it from dictionary.
  5. To renew sessions put renew session method in your code.

For extream security

Have private/public key pair, client will encrypt entire post data using public key and you will decrypt using private key.

Ungodly answered 27/4, 2009 at 23:46 Comment(0)
R
0

First you may want to add WCF tracing to your service to get more details. Second, I believe the problem could be that you're sending user credentials in clear text and WCF does not allow the transmission of user credentials in clear text over unsecured transport channel. So either try using https or specify an encryption algorithm to secure user credentials over http.

Rheinland answered 28/4, 2009 at 0:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.