WCF Authenticate Preemptively

Normally when you send a request to a service that uses basic authenication, you will initially send a service request without authorization header. The service will respond with a Http 500 error and will send the client an authentication request. This all happens automatically. The client will send the service request again. This time an authorization header goes with the request. When all things are well, the service responds with a Http 200 Success message.
Below you find the WCF code you will normally use:

string ditnopUrl = “http://dspimportservice.nl/”;
BasicHttpBinding binding = new BasicHttpBinding();
binding.Name = “Opdracht_OS_XOPBinding”;
binding.MaxReceivedMessageSize = 2147483647;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

EndpointAddress address = new EndpointAddress(ditnopUrl);
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = “user”;
loginCredentials.UserName.Password = “password”;

factory = new ChannelFactory(binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find();
factory.Endpoint.Behaviors.Remove(defaultCredentials); //remove default ones
factory.Endpoint.Behaviors.Add(loginCredentials); //add required ones
channel = factory.CreateChannel();

channel.Open();
Opdracht_OS_XOPRequest request = new Opdracht_OS_XOPRequest();
request.Opdrachtbericht = opdrachtbericht;
Opdracht_OS_XOPResponse responseDSP = channel.Opdracht_OS_XOP(request);

channel.Close();
factory.Close();

Maybe you expect you don’t have to use the code that adds the client credentials to the service behavior, but when you remove that code you will receive an error “The username is not provided. Specify username in ClientCredentials”.

Now suppose you use the same service with basic authentication, but this time you specify ‘authenticate preemptively’. This can happen if you build a non-WCF service that you host on let’s say Tomcat. In this case the service expects an authorization header on the initial request. An authentication request will not be sent. The service simply responds with a Http 500 error. So, what you need to do now, is specifically set the authorization header and send it with the request. The code below shows the code you can use:

string ditnopUrl = “http://dspimportservice.nl/”;
BasicHttpBinding binding = new BasicHttpBinding();
binding.Name = “Opdracht_OS_XOPBinding”;
binding.MaxReceivedMessageSize = 2147483647;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

EndpointAddress address = new EndpointAddress(ditnopUrl);
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = “user”;
loginCredentials.UserName.Password = “password”;

factory = new ChannelFactory(binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find();
factory.Endpoint.Behaviors.Remove(defaultCredentials); //remove default ones
factory.Endpoint.Behaviors.Add(loginCredentials); //add required ones
channel = factory.CreateChannel();

using (OperationContextScope scope = new OperationContextScope(channel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] = “Basic ” +
Convert.ToBase64String(Encoding.ASCII.GetBytes(“user : password”));
System.ServiceModel.OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;

channel.Open();
Opdracht_OS_XOPRequest request = new Opdracht_OS_XOPRequest();
request.Opdrachtbericht = opdrachtbericht;
Opdracht_OS_XOPResponse responseDSP = channel.Opdracht_OS_XOP(request);
}

channel.Close();

Add basic authentication to WCF service hosted in Azure

You can add basic authentication to your WCF service by adding a so-called HTTP module to the project with your service contract. The Http module intercepts the web service calls before they reach the actual service. The code of code file UserNameAuthenticator.cs is added at the bottom of the post. The only thing you will have to do next, is add the http module to the web.config:

<configuration>

<system.webServer>
<modules>
<add name=”BasicAuthenticationModule” type=”DSPGateway.ServiceContract.UserNameAuthenticator” />
</modules>
</system.webServer>

Code file:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;

namespace DSPGateway.ServiceContract
{
class UserNameAuthenticator : IHttpModule
{
public UserNameAuthenticator()
{
}
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
application.EndRequest += new EventHandler(this.OnEndRequest);
}
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
{
HttpApplication app = (HttpApplication)source;
//the Authorization header is checked if present
string authHeader = app.Request.Headers[“Authorization”];
if (!string.IsNullOrEmpty(authHeader))
{
string authStr = app.Request.Headers[“Authorization”];
if (authStr == null || authStr.Length == 0)
{
// No credentials; anonymous request
return;
}
authStr = authStr.Trim();
if (authStr.IndexOf(“Basic”, 0) != 0)
{
// header is not correct…we’ll pass it along and
// assume someone else will handle it
return;
}
authStr = authStr.Trim();
string encodedCredentials = authStr.Substring(6);
byte[] decodedBytes =
Convert.FromBase64String(encodedCredentials);
string s = new ASCIIEncoding().GetString(decodedBytes);
string[] userPass = s.Split(new char[] { ‘:’ });
string username = userPass[0];
string password = userPass[1];
if (username == “user” && password == “password“)
{
app.Context.User = new GenericPrincipal(new GenericIdentity(username, “DSP”), null);
}
else
{
DenyAccess(app);
return;
}
}
else
{
app.Response.StatusCode = 401;
app.Response.End();
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
//the authorization header is not present
//the status of response is set to 401 and it ended
//the end request will check if it is 401 and add
//the authentication header so the client knows
//it needs to send credentials to authenticate
if (HttpContext.Current.Response.StatusCode == 401)
{
HttpContext context = HttpContext.Current;
context.Response.StatusCode = 401;
context.Response.AddHeader(“WWW-Authenticate”, “Basic Realm”);
}
}
private void DenyAccess(HttpApplication app)
{
app.Response.StatusCode = 401;
app.Response.StatusDescription = “Access Denied”;

// Write to response stream as well, to give user visual
// indication of error during development
app.Response.Write(“401 Access Denied – invalid acccount”);
app.CompleteRequest();
}
public void Dispose()
{
}
}
}

Read More »

SvcUtil

I used SvcUtil to generate a service interface + data contracts from a WSDL using the following command:
SvcUtil OpdrachtXOP.wsdl /l:cs /serializer:XmlSerializer /syncOnly

Nothing special, the service contract and the datacontracts got generated. But, after I created a SOAP project in SoapUI, I saw a few very weird things:
– The Order attribute in my XmlElement attributes were ignored. As a result the elements showed up in alphabetic order.
– The names of the private fields were displayed, not the names of the public properties. So, eMailAddressField instead of EMailAddress.
– Choice elements were not working.

Anyways. In the end it turned out I had to remove ReplyAction=”*” from the service contract annotation. The problem had nothing to do with the generated data contracts. By removing ReplyAction=”*”, you make sure the correct namespaces are used.

[System.ServiceModel.OperationContractAttribute(Action = “http://sap.com/xi/WebService/soap1.1&#8221;, ReplyAction = “*”)]
[System.ServiceModel.FaultContractAttribute(typeof(dsplatform.nl.dsp.Common.DSPFault), Action = “http://sap.com/xi/WebService/soap1.1&#8221;, Name = “DSPFault”, Namespace = “http://dsplatform.nl/dsp/Common&#8221;)]
[System.ServiceModel.XmlSerializerFormatAttribute()]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(BerichtTypeXOP))]
Opdracht_OS_XOPResponse Opdracht_OS_XOP(Opdracht_OS_XOPRequest request);