I have a client (could be C# or PHP) that needs to be able to request an OAuth 2.0 access token (JWT for compatibility) from my STS (Thinktecture Identity Server), and then send that token to a webhttp endpoint in a WCF Service. The service will validate the token in a message inspector, and either throw an error or continue to a service method. I am using Thinktecture Identity Model, and RestSharp to send rest requests.
So far here is what I have:
Client:
var client = new OAuth2Client(
new Uri("https://mysts/issue/oauth2/token"),
"client",
"secret");
var response = client.RequestAccessTokenUserName("username", "password", "http://localhost:51696/");
var token = response.AccessToken;
var restClient = new RestClient("https://127.0.0.1:444/");
var restRequest = new RestRequest(Method.POST);
restRequest.AddHeader("Authorization", token);
restRequest.AddObject(new Request());
And I get the access token from the sts just fine, I'm not sure if I'm inserting the token in the authorization header correctly or not.
Message inspector:
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
#region new code
string binding = OperationContext.Current.EndpointDispatcher.ChannelDispatcher.BindingName;
if (binding == "http://tempuri.org/:SecureRestBinding")
{
// Check to see if there is an Authorization in the header, otherwise throw a 401
if (WebOperationContext.Current.IncomingRequest.Headers["Authorization"] == null)
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"myrealm\"");
throw new WebFaultException<string>("No username or password was provided", HttpStatusCode.Unauthorized);
}
else
{
//Code to validate oauth token?
}
}
This makes sure to only check REST messages, SOAP messages are handled via a security token handler. I need to fill in code to validate the oauth token, but I cant seem to find a good example anywhere.
web.config in case its relevant:
<system.serviceModel>
<services>
<service behaviorConfiguration="MyServiceBehavior" name="MyWCFWebRole.MyWcfService">
<endpoint address="/REST/"
behaviorConfiguration="MyRestBehavior"
binding="webHttpBinding" bindingConfiguration="RESTSSLBinding"
name="RestSSLEndpoint" bindingName="SecureRestBinding"
contract=MyWCFWebRole.MyWcfServic" />
<endpoint address="/SOAP/"
binding="basicHttpBinding"
bindingConfiguration="secureHttpBinding"
name="SecureHttpEndpoint"
bindingName="SecureHttpBinding"
contract="MyWCFWebRole.MyWcfServic" />
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="RESTSSLBinding">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</webHttpBinding>
<basicHttpBinding>
<binding name="secureHttpBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
<ws2007FederationHttpBinding>
<binding name="">
<security mode="Message">
<message>
<issuerMetadata address="mex_address" />
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="MyRestBehavior">
<webHttp helpEnabled="true" faultExceptionEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization principalPermissionMode="Always" />
<serviceCredentials useIdentityConfiguration="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment minFreeMemoryPercentageToActivateService="0" aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<protocolMapping>
<add scheme="http" binding="ws2007FederationHttpBinding" />
</protocolMapping>
</system.serviceModel>
<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="MyService.svc" />
</files>
</defaultDocument>
<modules runAllManagedModulesForAllRequests="true" />
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="false" />
</system.webServer>
Any help is appreciated. Using .net 4.5
Related
I have a WCF service with net.tcp endpoints using custom usernamePassswordValidator, custom authorization and TransportWithMessageCredential with credential type "Username" (see below).
Server and client work fine - unless the time skew between server and client machine are more than 5 minutes.
Now I try to set the max skew time in code. I tried to adapt code snippets intended for WSHttpBindings from MSDN and used the custom binding on server and client:
binding = GetSecuredBindingFromServerOrClient();
CustomBinding myCustomBinding = new CustomBinding(binding);
var security = myCustomBinding.Elements.Find<TransportSecurityBindingElement>(); // TransportSecurityBindingElement or SecurityBindingElement
security.LocalClientSettings.MaxClockSkew = timeSkew;
security.LocalServiceSettings.MaxClockSkew = timeSkew;
security.LocalServiceSettings.DetectReplays = false;
security.IncludeTimestamp = false;
// on client: use this custom binding in channel factory
var channelFactory = new ChannelFactory<ICheckerService>(customBinding, someAddress);
// on server: Update binding with customBinding
endpoint.Binding = myCustomBinding;
Still the connection fails with a MessageSecurityException when there is a time skew for more than 5 minutes (default value). I set also IncludeTimestamp to false or true but neither of them improved the situation.
The server behavior is:
<behavior name="customUserNamePasswordSecurityBehavior">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MySecurity.BasicAuthenticationValidator, MySecurity.Services"/>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="Security.CustomAuthorizationPolicy, MySecurity.Services"/>
</authorizationPolicies>
</serviceAuthorization>
</behavior>
Then endpoint bindings are:
<binding name="tcpUserNameAuthentication">
<reliableSession enabled="true"/>
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
Did anybody get the time skew working based on the above configuration with TransportWithMessageCredential and net.tcp? Or is there a basic misunderstanding?
On my side, MaxClockSkew works well if I use NetTcp protocol, which requires a certificate on the server-side (need to add the management permission of the private key to the account running the service) and username/password on the client-side.
At first, I transform the Nettcpbinding to Custombinding.
<customBinding>
<binding name="mybinding">
<security authenticationMode="SecureConversation">
<localClientSettings maxClockSkew="00:07:00" />
<localServiceSettings maxClockSkew="00:07:00" />
<secureConversationBootstrap authenticationMode="UserNameForCertificate">
<localClientSettings maxClockSkew="00:30:00" />
<localServiceSettings maxClockSkew="00:30:00" />
</secureConversationBootstrap>
</security>
<binaryMessageEncoding></binaryMessageEncoding>
<tcpTransport />
</binding>
</customBinding>
Then I invocate the service with the client proxy class when I change the time on the client-side, it works well when the client time varies within 7minutes on the server-side. if I didn't set up the MaxClockSkew on the server-side. it only works within 5minutes the server-side time.
Please refer to the below example, wish it is useful to you.
Server-side(console application)
class Program
{
static void Main(string[] args)
{
using (ServiceHost sh=new ServiceHost(typeof(MyService)))
{
sh.Open();
Console.WriteLine("Service is ready....");
Console.ReadLine();
sh.Close();
}
}
}
[ServiceContract]
interface IService
{
[OperationContract]
string GetData();
}
public class MyService : IService
{
public string GetData()
{
return DateTime.Now.ToString();
}
}
public class MyValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "jack" || password != "123456")
{
throw new Exception("My Error");
}
}
}
App.config
<system.serviceModel>
<services>
<service name="Server1.MyService" behaviorConfiguration="mybehavior">
<endpoint address="" binding="customBinding" contract="Server1.IService" bindingConfiguration="mybinding"></endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:800"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<customBinding>
<binding name="mybinding">
<security authenticationMode="SecureConversation">
<localClientSettings maxClockSkew="00:07:00" />
<localServiceSettings maxClockSkew="00:07:00" />
<secureConversationBootstrap authenticationMode="UserNameForCertificate">
<localClientSettings maxClockSkew="00:30:00" />
<localServiceSettings maxClockSkew="00:30:00" />
</secureConversationBootstrap>
</security>
<binaryMessageEncoding></binaryMessageEncoding>
<tcpTransport />
</binding>
</customBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="mybehavior">
<serviceMetadata />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="5ba5022f527e32ac02548fc5afc558de1d314cb6" x509FindType="FindByThumbprint" storeLocation="LocalMachine" storeName="My"/>
<userNameAuthentication customUserNamePasswordValidatorType="Server1.MyValidator,Server1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Client-side.
ServiceReference1.ServiceClient client = new ServiceClient();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
try
{
var result = client.GetData();
Console.WriteLine(result);
}
catch (Exception)
{
throw;
}
App.config(auto-generated)
<system.serviceModel>
<bindings>
<customBinding>
<binding name="CustomBinding_IService">
<security defaultAlgorithmSuite="Default" authenticationMode="SecureConversation"
requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
requireSignatureConfirmation="false" canRenewSecurityContextToken="true">
<secureConversationBootstrap defaultAlgorithmSuite="Default"
authenticationMode="UserNameForCertificate" requireDerivedKeys="true"
includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
requireSignatureConfirmation="false">
<localClientSettings detectReplays="true" />
<localServiceSettings detectReplays="true" />
</secureConversationBootstrap>
<localClientSettings detectReplays="true" />
<localServiceSettings detectReplays="true" />
</security>
<binaryMessageEncoding />
<tcpTransport />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="net.tcp://10.157.13.69:800/" binding="customBinding"
bindingConfiguration="CustomBinding_IService" contract="ServiceReference1.IService"
name="CustomBinding_IService">
<identity>
<certificate encodedValue="blablabla" />
</identity>
</endpoint>
</client>
</system.serviceModel>
Feel free to let me know if there is anything I can help with.
I've been trying to figure this out for a couple of days. I know that I'm close but can't quite figure out what I'm missing.
The service is in a website hosted on IIS
Basic authentication is enabled for the site, and all others including anonymous authentication is disabled.
I have created a self signed certificate and set up a https binding
I have overridden the Validate method of the UsernamePasswordValidator but when I attach a break point it isn't reached. So my problem is likely something to do with this.
When I attempt to access my service method at https://localhost/Services/JobSiteService.svc/rest/get I am continually prompted for username and password as i receive a 401 unauthorized error.
Can anyone see what I'm doing wrong?
Appropriate code below:
Web.Config
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="Validity.WebService.JobSiteService" behaviorConfiguration="SecureRestBehavior">
<endpoint address="rest" binding="webHttpBinding" behaviorConfiguration="RESTBehavior" bindingConfiguration="webHttpTransportSecurity" contract="Validity.WebService.Contracts.IJobSiteService" />
<endpoint address="meta" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureRestBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Validity.WebService.IdentityValidator, Validity.WebService" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="RESTBehavior" >
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="webHttpBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
Service Contract:
[ServiceContract]
public interface IJobSiteService
{
[OperationContract]
[WebInvoke(Method="GET", ResponseFormat=WebMessageFormat.Json)]
List<JobSite> Get();
}
Custom Validator (it's in the namespace "Validity.WebService" but the code formatting broke when I included it.):
public override void Validate(string userName, string password)
{
using (var context = new ValidityContext())
{
using (var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context)))
{
var user = userManager.Find(userName, password);
if (user == null)
{
var msg = String.Format("Unknown Username {0} or incorrect password {1}", userName, password);
throw new FaultException(msg);//the client actually will receive MessageSecurityException. But if I throw MessageSecurityException, the runtime will give FaultException to client without clear message.
}
else
{
SessionOperationContext.Current.Items["CurrentUser"] = user;
}
}
}
}
Any help with this would be greatly appreciated.
Finally figured this out.
The solution was to disable basic authentication on the IIS server and override the CheckAccessCore method of the ServiceAuthorizationManager to validate the user.
I m getting the following error when I did set the Windows Authentication enable and anonymous to disabled in IIS.
The authentication schemes configured on the host
('IntegratedWindowsAuthentication') do not allow those configured on
the binding 'BasicHttpBinding' ('Anonymous'). Please ensure that the
SecurityMode is set to Transport or TransportCredentialOnly.
Additionally, this may be resolved by changing the authentication
schemes for this application through the IIS management tool, through
the ServiceHost.Authentication.AuthenticationSchemes property, in the
application configuration file at the
element, by updating the ClientCredentialType property on the
binding, or by adjusting the AuthenticationScheme property on the
HttpTransportBindingElement.
My Wcf Service's web.config is as follows...
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpEndpointBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint binding="basicHttpBinding"
bindingConfiguration="BasicHttpEndpointBinding"
contract="Test.IService1" name="BasicHttpEndpoint" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceAuthenticationManager
authenticationSchemes="IntegratedWindowsAuthentication"/>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpBinding" scheme="http" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
Please advice..
In .Net 4.0+, Simplified WCF configuration uses the 'anonymous' configurations when configurations are not explicitly set on a per-services basis in the <services> section. If you remove the name="BasicHttpEndpointBinding" from the <binding> element, or if you duplicate that <binding> element as a new element with no name attribute, it will become the default, anonymous binding that your WCF services will use. This is often useful in cases where you need to serve as well as consume WCF services that may not all have the same config - but at least you can set a default config for the services that do not have a specific config set. The default/anonymous concept is also applicable to <behavior> elements.
<bindings>
<basicHttpBinding>
<binding> <!--Notice, no name attribute set-->
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
Also, I might add that if your WCF services require authentication, this means that you will either need to consume the service using a real user account, or you will need to grant the the DOMAIN\CLIENTCOMPUTERNAME$ account access to the service - so, perhaps the proper solution for many people may be to alter the configuration to instead allow anonymous access (which is not discussed in my answer). Still, I do sometimes actually elect to secure my WCF services with Windows (Kerberos) authentication.
Adding this worked for me.
<bindings>
<webHttpBinding>
<binding>
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</webHttpBinding>
</bindings>
I got this error when updating from .NET 4.0 to .NET 4.5.2. I changed the clientCredentialType from
<security mode="TransportCredentialOnly">
<transport clientCredentialType="None"/>
</security>
to
<security mode="TransportCredentialOnly">
<transport clientCredentialType="InheritedFromHost"/>
</security>
However, setting clientCredentialType="Windows" works equally well.
I had the same issue when consuming already existing WCF web URL.
I tried all the answers mentioned here , but all in all finally only two things helped.
Changing the setting in "Turn windows Features on and off".
Enabling Anonymous authentication along with Windows Authentication in Local IIS server.
<services>
<service name="Test.Service1" behaviorConfiguration="TestName">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpEndpointBinding" contract="Test.IService1" />
</service>
</services>
It solved my problem.
Like the other answers, I needed to update the binding in my Web.config to this:
<basicHttpBinding>
<binding name="basicHttpBindin1">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
But I also needed to update my binding's instantiation:
var binding = new BasicHttpBinding { MaxReceivedMessageSize = 1000000, ReaderQuotas = { MaxDepth = 200 } };
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
I had add a webHttpBinding and point my endpoint to that, which the security settings needed to work. Without that my endpoint used the default WCF config bindings:
<services>
<service behaviorConfiguration="ServiceBehavior" name="Service">
<endpoint address="" binding="webHttpBinding" contract="IService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<bindings>
<webHttpBinding>
<binding>
<!--Notice, no name attribute set-->
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</webHttpBinding>
</bindings>
I'm not entirely sure why, but when I added the 'Factory' attribute to my .SVC file (you need to explicitly drag it to Visual Studio), everything just works - without any changes to default settings in Web.config!
I added Factory="System.ServiceModel.Activation.WebServiceHostFactory" so my .SVC file went from this:
<%# ServiceHost Language="C#" Debug="true" Service="ServiceNameSpace.ServiceName" CodeBehind="ServiceName.svc.cs" %>
to this:
<%# ServiceHost Language="C#" Debug="true" Service="ServiceNameSpace.ServiceName" CodeBehind="ServiceName.svc.cs" Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>
The only side effect seems to be that when you click on the .SVC file in the browser, you get an 'Endpoint not found' error, but the service works fine when you invoke it correctly anyway. As mentioned previously, I'm using a default Web.config with .NET 4.6 (Simplified WCF configuration), so I may yet need to add endpoint details for that to work again.
I've got a WebService with ASP.NET sites and WCF services in the same web.config. Until now, I was able to use the ASP.NET impersionation in the WCF services by setting
<system.web>
<compilation targetFramework="4.0" debug="false"/>
<!-- switch custom errors of-->
<identity impersonate="true"/>
<customErrors mode="Off"/>
</system.web>
However, now (for other reasons-> Cookieless Session state for the ASP.NET part) I have to set the
aspNetCompatibilityEnabled="true"
option to false. With this I loose the ASP.NET impersionation for the WCF services.
One of my WCF services needs impersionation for IO operations on the server...
I would like to know how to get the same impersionation I had before by directly defining it on the WCF service configuration.
What I have tried (unsucessfully) is to set
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
on the implementation of the methods in the WCF service and then specifying
<endpoint address="" binding="wsHttpBinding" contract="IService">
<identity>
<servicePrincipalName value="HOST/YourMachineName" />
<dns value="" />
</identity>
</endpoint>
in the web.config (obviously with the correct values for my service), as described in http://msdn.microsoft.com/en-us/library/ff650591.aspx.
However, the WCF service can not be called anymore after this... It tells me that the WsHttpBinding does not offer an identity for the contract.
Am I missing something important?
Edit: Translation of the error message:
: The contract operation '{0}' requires Windows identity for automatic impersonation. A Windows identity that represents the caller is not provided by binding ('{1}','{2}') for contract ('{3}','{4}'.
(The original error message was german...)
Try adding someting similar to this
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="DelegationBehaviour">
<clientCredentials>
<windows allowNtlm="false" allowedImpersonationLevel="Delegation"></windows>
</clientCredentials>
<dataContractSerializer maxItemsInObjectGraph="4194304"></dataContractSerializer>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_SampleWebService" >
<readerQuotas maxArrayLength="16384" maxBytesPerRead="4096" maxDepth="32" maxNameTableCharCount="16384" maxStringContentLength="8192"></readerQuotas>
<security mode="TransportCredentialOnly">
<message algorithmSuite="Default" clientCredentialType="UserName"></message>
<transport clientCredentialType="Windows" proxyCredentialType="None" realm=""></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://server/WebServices/Service/Service.svc" behaviorConfiguration="DelegationBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_SampleWebService" contract="SampleWS" name="BasicHttpBinding_SampleEndpoint"></endpoint>
</client>
</system.serviceModel>
This is the server side code
<system.serviceModel>
<services>
<service behaviorConfiguration="CustomBehavior" name="CustomWebService">
<endpoint address="" behaviorConfiguration="" binding="basicHttpBinding" bindingConfiguration="basicHttpBinding_Service" contract="WebService"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="basicHttpBinding_Service" maxReceivedMessageSize="4194304" receiveTimeout="00:30:00">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CustomBehavior">
<dataContractSerializer maxItemsInObjectGraph="4194304" ignoreExtensionDataObject="True"/>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true"/>
<serviceAuthorization impersonateCallerForAllOperations="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
As well as having these over our WebMethods
<WebMethod(), OperationContract(), OperationBehavior(Impersonation:=ImpersonationOption.Required)> _
Works for us
Well, in the end I just made the binding use Windows authentication:
<security mode="TransportWithMessageCredential">
<message negotiateServiceCredential="false" clientCredentialType="Windows" algorithmSuite="Default"/>
<transport clientCredentialType="None" proxyCredentialType="None" realm="" />
</security>
and passed a specific Windows user/pwd combination in the client:
channelFactory.Credentials.Windows.ClientCredential = new NetworkCredential(#"", "", "");
channelFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
Additionally I had to specifically use the newly impersonated user in the code of the web service:
using (var imp = ServiceSecurityContext.Current.WindowsIdentity.Impersonate())
{
// do IO here
}
Well, the actual (underlying) question still remains:
How is it possible to emulate the ASP.NET functionality correctly...
For the moment I'm ok with the solution, however I've got the feeling that I've missed an important point about the ASP.NET impersonation.
Thanks a lot to Iain, although it wasn't exactly the correct answer, it at least got me on the right track!
I've created a simple WCF Service which is hosted in IIS. Now i want to use my own userName authentication.
My web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="WcfIIsBasicAuthTest.Service1" behaviorConfiguration="MyBehavior">
<endpoint address=""
binding="basicHttpBinding"
contract="WcfIIsBasicAuthTest.IService1"
bindingConfiguration="MyBinding"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfIIsBasicAuthTest.MyValidator, WcfIIsBasicAuthTest"/>
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
The Validator
namespace WcfIIsBasicAuthTest
{
public class MyValidator :UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if(!(userName == "test" && password == "test"))
{
throw new SecurityTokenException("Validation Failed!");
}
}
}
}
If i start this WCF Service from within visual studio, i get the following error: The authentication schemes configured on the host ('Ntlm, Anonymous') do not allow those configured on the binding 'BasicHttpBinding' ('Basic').
If i try to connect to the service if it is hosted in IIS, i get error messages depending on which authentication type is set, but it doesn't work at all.
Error if only Anonymous authentication is enabled: The authentication schemes configured on the host ('Anonymous') do not allow those configured on the binding 'BasicHttpBinding' ('Basic').
If i set Basic Authentication in IIS, it demands a valid local user which i don't want to provide(since i want to write my own userprovider).
Any hints/links how to setup such a basic auth userprovider with wcf and iis?
Can you set the below configuration for using your own UserNameValidator:
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
For that to work with basicHttpBinding you would need to have HTTPS setup as WCF doesnt allow username password being passed over the channel in clear text. The other alternative is to use wsHttpBinding.