I have been following this tutorial in order to get username authentication with transport security working in my WCF service. The tutorial however refers to using basicHttpBinding
which is unacceptable - I require wsHttpBinding
.
The idea is to have a custom BasicAuthenticationModule
on WCF service which would read the "Authorization" header from the HTTP request and perform the auth process according to "Authorization" header contents. The problem is that "Authorization" header is missing!
I have implemented IClientMessageInspector
via custom behavior in order to manipulate outgoing messages and add custom SOAP headers. I have added the following code in BeforeSendRequest
function:
HttpRequestMessageProperty httpRequest = request.Properties.Where(x => x.Key == "httpRequest").Single().Value;
httpRequest.Headers.Add("CustomHeader", "CustomValue");
This should work and according to many web resources, it works for basicHttpBinding
but not wsHttpBinding
. When I say "works", I mean that the header is successfully received by WCF service.
This is the simplified function which inspects the received HTTP message on WCF service side:
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))
{
app.Response.StatusCode = 401;
app.Response.End();
}
}
Bottom posts of this thread dated september 2011 say that this is not possible with wsHttpBinding
. I don't want to accept that response.
As a side note, if I use the Basic Authentication Module built in IIS and not the custom one, I get
The parameter 'username' must not contain commas.** error message when trying
Roles.IsInRole("RoleName")
or `[PrincipalPermission(SecurityAction.Demand, Role = "RoleName")]
probably because my PrimaryIdentity.Name
property contains the certificate subject name as I am using TransportWithMessageCredential
security with certificate-based message security.
I am open for suggestions as well as alternate approaches to the problem. Thanks.
UPDATE
As it seems, the HTTP header gets read correctly later throughout the WCF service code.
(HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties["httpRequest"]
contains my custom header. However, this is already message-level.. How to pass the header to transport authentication routine?
UPDATE 2
After a bit of a research, I came to a conclusion that, when a web browser receives HTTP status code 401, it presents me with the login dialog where I can specify my credentials. However a WCF client simply throws an exception and doesn't want to send credentials. I was able to verify this behavior when visiting https://myserver/myservice/service.svc
in Internet Explorer. Tried to fix using information from this link but to no avail. Is this a bug in WCF or am I missing something?
EDIT
Here are relevant sections from my system.servicemodel
(from web.config
) - I am pretty sure I got that configured right though.
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" />
</clientCertificate>
<serviceCertificate findValue="server.uprava.djurkovic-co.me" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
</serviceCredentials>
<serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="AspNetSqlRoleProvider" />
</behavior>
</serviceBehaviors>
................
<wsHttpBinding>
<binding name="EndPointWSHTTP" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="20480000" maxReceivedMessageSize="20480000" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="20480000" maxStringContentLength="20480000" maxArrayLength="20480000" maxBytesPerRead="20480000" maxNameTableCharCount="20480000" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Basic" />
<message clientCredentialType="Certificate" negotiateServiceCredential="true" algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
............
<service behaviorConfiguration="ServiceBehavior" name="DjurkovicService.Djurkovic">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="EndPointWSHTTP" name="EndPointWSHTTP" contract="DjurkovicService.IDjurkovic" />
</service>
The exception returned by the service is:
The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic Realm,Negotiate,NTLM'. (The remote server returned an error: (401) Unauthorized.)