Long story short:
My WCF clients should be able to provide both username and certificate to a service hosted in IIS, where I should use that information to validate requests using a custom policies.
Complete story:
I have the need to authenticate some WCF clients to verify if they can execute operations.
We have two kinds of clients: WPF applications and a web application. We would like to do the following:
The web application uses a certificate trusted by the service so that it is recognized as a special user with all permissions (the web application already verifies permissions by itself and we wouldn't like to touch it by now)
The WPF clients authenticate themselves with username/password provided by the user
In the implementation of the operations, I would like to verify if the certificate was provided (then I recognize the "super user"), otherwise fallback to username/password authentication.
Services are hosted in IIS 7 and we need to use NetTcpBinding.
I was able to implement the username validation, but the problem is that the AuthorizationContext inspected by the service contains only identity information, and not the certificate.
The following code is used on the client side to initialize the creation of channels (from a spike I'm using to test the solution):
var factory = new ChannelFactory<T>(this.Binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
factory.Endpoint.Behaviors.Remove(defaultCredentials);
var loginCredentials = new ClientCredentials();
loginCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
loginCredentials.UserName.UserName = username;
loginCredentials.UserName.Password = password;
if (useCertificate)
{
loginCredentials.SetCertificate();
}
factory.Endpoint.Behaviors.Add(loginCredentials);
return factory.CreateChannel();
With the SetCertificate extension being implemented like this:
public static void SetCertificate(this ClientCredentials loginCredentials)
{
loginCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SecureWcfClient");
}
This is the configuration of the web application hosting the services:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="SecureBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="Test"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="AuthenticationProtectedService.Security.CertificateValidator, AuthenticationProtectedService.Security"/>
</clientCertificate>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="AuthenticationProtectedService.Security.UserNamePassValidator, AuthenticationProtectedService.Security" />
</serviceCredentials>
<serviceAuthorization serviceAuthorizationManagerType="AuthenticationProtectedService.Security.CertificateAuthorizationManager, AuthenticationProtectedService.Security"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding>
<security mode="None"/>
</binding>
<binding name="SecureNetTcp">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<service
name="AuthenticationProtectedService.Services.OneWayServiceB"
behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="wsHttpBinding"
contract="AuthenticationProtectedService.ServiceModel.IOneWayServiceB">
</endpoint>
</service>
<service
name="AuthenticationProtectedService.Services.DuplexServiceB" behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="netTcpBinding"
bindingConfiguration="SecureNetTcp"
contract="AuthenticationProtectedService.ServiceModel.IDuplexServiceB">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
Finally, this is the implementation of the custom authorization manager (I also tried with a custom certificate validator but the function was never run)
public class CertificateAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
if (!base.CheckAccessCore(operationContext))
{
return false;
}
string thumbprint = GetCertificateThumbprint(operationContext);
// I'd need to verify the thumbprint, but it is always null
return true;
}
private string GetCertificateThumbprint(OperationContext operationContext)
{
foreach (var claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Thumbprint, Rights.Identity))
{
string tb = BitConverter.ToString((byte[])claim.Resource);
tb = tb.Replace("-", "");
return tb;
}
}
return null;
}
}
I think that the problem could be in the clientCredentialType property of the nettcpbinding.Security.Message node on the service configuration, but I don't see the option to use both Certificate and Username withing the Message security.
Any help appreciated, thanks
Remark: a specific goal of the project is to have very low level impact on server setup and in general in the system, so also SSL should be avoided if possible.
try out this link http://msdn.microsoft.com/en-us/library/ms733099.aspx ...it might resolve your issue where in you can have different binding configuration for same binding type and associate the same to different endpoints as per your need.
Related
I have a MVC app talking to ACS to get token for authentication. It's a claim based application. This works perfectly fine.
I am trying to call WCF service from MVC once authenticated with same taken so that i can use same claims for authorization.
MVC code is as below
var context = (BootstrapContext)identity.BootstrapContext;
var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.Message);
binding.Security.Message.IssuedKeyType = SecurityKeyType.SymmetricKey;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.IssuerBinding = new WS2007FederationHttpBinding();
EndpointAddress acsEndPoint =
new EndpointAddress("https://ACS namespace/v2/wsfederation");
binding.Security.Message.IssuerAddress = acsEndPoint;
binding.Security.Message.IssuedTokenType = "urn:ietf:params:oauth:token-type:jwt";
ChannelFactory<IService1> factory =
new ChannelFactory<IService1>(binding, new EndpointAddress("https://localhost/TestWCF/Service1.svc"));
factory.Credentials.SupportInteractive = false;
factory.Credentials.UseIdentityConfiguration = true;
var proxy = factory.CreateChannelWithIssuedToken(context.SecurityToken);
proxy.GetData(1);
WCF web config is as below
<system.serviceModel>
<services>
<service name="TestWCF.Service1">
<endpoint address="" behaviorConfiguration="webHttpAutoFormat" binding="ws2007FederationHttpBinding" bindingConfiguration="secureHttpBinding" contract="TestWCF.IService1"/>
<endpoint address="soap" binding="basicHttpBinding" contract="TestWCF.IService1" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<bindings>
<ws2007FederationHttpBinding>
<binding name="secureHttpBinding">
<security mode="None">
<message establishSecurityContext="false" issuedKeyType="SymmetricKey" issuedTokenType="urn:ietf:params:oauth:token- type:jwt">
<issuerMetadata address="https://ACS namespace/v2/wstrust/mex"></issuerMetadata>
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials useIdentityConfiguration="true"></serviceCredentials>
<serviceAuthorization principalPermissionMode="Always" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webHttpAutoFormat">
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true">
<serviceActivations>
<add relativeAddress="Service1.svc" service="TestWCF.Service1" />
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
Please note my WCF service is not HTTPS also I am using JWT token from ACS. No certificates.
I get below error
The provided URI scheme 'https' is invalid; expected 'http'.
Parameter name: via
Can anyone help?
You are currently initializing your binding with
var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.Message)
Try changing to
var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.TransportWithMessageCredential)
From (MSDN - WS Transport With Message Credential):
By default, the wsHttpBinding binding provides HTTP communication.
When configured for transport security, the binding supports HTTPS
communication. HTTPS provides confidentiality and integrity protection
for the messages that are transmitted over the wire. However the set
of authentication mechanisms that can be used to authenticate the
client to the service is limited to what the HTTPS transport supports.
Windows Communication Foundation (WCF) offers a
TransportWithMessageCredential security mode that is designed to
overcome this limitation. When this security mode is configured, the
transport security is used to provide confidentiality and integrity
for the transmitted messages and to perform the service
authentication. However, the client authentication is performed by
putting the client credential directly in the message. This allows you
to use any credential type that is supported by the message security
mode for the client authentication while keeping the performance
benefit of transport security mode.
Your web config should have this instead for <ws2007FederationHttpBinding>:
<ws2007FederationHttpBinding>
<binding name="secureHttpBinding">
<security mode="TransportWithMessageCredential">
<message establishSecurityContext="false" issuedKeyType="SymmetricKey" issuedTokenType="urn:ietf:params:oauth:token- type:jwt">
<issuerMetadata address="https://ACS namespace/v2/wstrust/mex"></issuerMetadata>
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
See also the following answer for some additional info as well: StackOverflow - The provided URI scheme 'https' is invalid; expected 'http'. Parameter name: via
I have a .Net c# client that needs to consume a Java web service from a third party. They require both a client cert and user name and password. I have the cert set up but constantly get 401 Unauthorized because I don't think the username and password are actually being attached to the request. It seems like WCF expects one or the other but not both cert and username/password. Surely I'm missing something.
<bindings>
<binding name="CC2WebSoap">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</bindings>
<client>
<endpoint address="https://url_goes_here.com"
binding="basicHttpBinding"
bindingConfiguration="CC2WebSoap"
contract="acontract"
name="CC2WebSoap"
behaviorConfiguration="SecureClientBehavior"/>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="SecureClientBehavior">
<clientCredentials>
<clientCertificate findValue="mythumbprint" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
try
{
CC2WebSoap client= new CC2WebSoapClient("CC2WebSoap");
client.ClientCredentials.UserName.UserName = "username";
client.ClientCredentials.UserName.Password = "password";
request = BuildRequest();
response = client.DoSomething(request);
}
catch(Exception e){ // Always get 401 exception here. }
This turned out to be reasonably simple by adding a MessageInspector and the related classes to get WCF to attach the username and password to the headers before each request. Specifically, I followed the advice in the blog post below exactly.
Using a MessageInspector To modify HTTP Headers
This may seem like a really easy question but I can't seem to figure it out at all.
I'm trying to create a new WCF service, and I'm new to having to secure them. I'm using a custom username/password for authentication. The problem [right now anyways] that I seem to be running into is that I can't figure out how to define the service to use the WSHttpBinding (on the service side, not the client side).
Am I missing something incredibly simple? Any pointers and/or recommendations would be greatly appreciated!
EDIT
Here's my code so far:
IAccountService
[ServiceContract]
public interface IAccountService
{
[OperationContract]
bool IsCardValid(string cardNumber);
[OperationContract]
bool IsAccountActive(string cardNumber);
[OperationContract]
int GetPointBalance(string cardNumber);
}
Service web.config
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
<StructureMapServiceBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="StructureMapServiceBehavior" type="Marcus.Loyalty.WebServices.Setup.StructureMapServiceBehavior, Marcus.Loyalty.WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
<services>
<service name="Marcus.Loyalty.WebServices.Account.IAccountService">
<endpoint address=""
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_Config"
contract="Marcus.Loyalty.WebServices.Account.IAccountService"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_Config"/>
</wsHttpBinding>
</bindings>
</system.serviceModel>
Testing app (console app)
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please enter card number");
var number = Console.ReadLine();
var endPoint = new EndpointAddress("http://localhost:59492/Account/AccountService.svc");
var binding = new WSHttpBinding(SecurityMode.Message);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
var cf = new ChannelFactory<IAccountService>(binding, endPoint);
cf.Credentials.UserName.UserName = "testuser";
cf.Credentials.UserName.Password = "Password1!";
var service = cf.CreateChannel();
var balance = service.IsAccountActive(number);
Console.WriteLine("\nBALANCE: {0:#,#}", balance);
Console.Write("\n\nPress Enter to continue");
Console.Read();
}
}
Testing app app.config
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="BasicHttpBinding_IAccountService" />
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:59492/Account/AccountService.svc"
binding="wsHttpBinding" bindingConfiguration="BasicHttpBinding_IAccountService"
contract="ServiceReference1.IAccountService" name="BasicHttpBinding_IAccountService" />
</client>
</system.serviceModel>
</configuration>
You need to define the abc (address, binding, contract) configuration into de web.config file (you can also do it programmatically. the b part, the binding, you can specify the wsHttpBinding
<system.serviceModel>
<services>
<service name = "MyNamespace.MyService">
<endpoint
address = "http://localhost:8000/MyService"
binding = "wsHttpBinding"
contract = "MyNamespace.IMyContract" />
</service>
</services>
</system.serviceModel>
If you wish to enable security in a proper way, there is a lot of literature and options. You can use certificates, windows based, tokens, ... passing a username & password like a parameter could not be the best way to do it.
There is an extensive sample on MSDN (How to: Specify a Service Binding in code) - but basically, you need to have:
your service contract (IMyService)
an implementation of that service (MyService)
a code where you create your ServiceHost to host your service
You got all of that? Great!
In that case, just do something like this:
// Specify a base address for the service
string baseAddress = "http://YourServer/MyService";
// Create the binding to be used by the service.
WsHttpBinding binding1 = new WsHttpBinding();
using(ServiceHost host = new ServiceHost(typeof(MyService)))
{
host.AddServiceEndpoint(typeof(IMyService), binding1, baseAddress);
host.Open();
Console.ReadLine();
}
and now you should have your service host up and running, on your chosen base address and with the wsHttpBinding defined in code.
I have a service which is hosted in IIS. It is configured by Web.config.
I have created a custom UserNamePassValidator which works if I I have the logic in the validate method. But I want the logic in another project and injected in using DI as below.
public class UserNamePassValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
{
private readonly ISystemAuthentication _systemAuthentication;
public UserNamePassValidator(ISystemAuthentication systemAuthentication)
{
_systemAuthentication = systemAuthentication;
}
public override void Validate(string userName, string password)
{
_systemAuthentication.Validate(userName, password))
}
}
I am using the Autofac WCF integration.
var builder = new ContainerBuilder();
builder.RegisterType<AuthenticationService>().As<IAuthenticationService>();
builder.Register(c => new SystemAuthentication()).As<ISystemAuthentication>();
builder.Register(c => new UserNamePassValidator(c.Resolve<ISystemAuthentication>()));
AutofacHostFactory.Container = builder.Build();
When I browse to the service i receive the following error:
[MissingMethodException: No parameterless constructor defined for this object.]
web.config behaviour;
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyNamespace.UserNamePassValidator, service" />
I have read the following related post but the example is a self hosted service:
How to inject an object into a WCF validator class
EDIT
<system.serviceModel>
<services>
<service behaviorConfiguration="Namespace.AuthenticationServiceBehaviour" name="Namespace.AuthenticationService" >
<endpoint address="" binding="wsHttpBinding" contract="Namespace.IAuthenticationService" bindingConfiguration="SafeServiceConf">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Namespace.AuthenticationServiceBehaviour">
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="AuthenticationService"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="Namespace.UserNamePassValidator, Service" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="SafeServiceConf" maxReceivedMessageSize="65536">
<readerQuotas maxStringContentLength="65536" maxArrayLength="65536" maxBytesPerRead="65536" />
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
SystemAuthentication class
public class SystemAuthentication : ISystemAuthentication
{
public bool Validate(string userName, string password)
{
// removed code for abbreviation
return true;
}
WCF Authentication Service
public class AuthenticationService : IAuthenticationService
{
public bool Authenticate(string email, string password)
{
// removed for abbreviation
return true;
}
}
From the help of this post UserNamePasswordValidator: When DI and Framework collide
From the XML configuraiton i removed:
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyNamespace.UserNamePassValidator, service" />
I added the behaviour to the AutoFacHostFactory service host
IContainer container = builder.Build();
AutofacHostFactory.Container = container;
AutofacHostFactory.HostConfigurationAction = host =>
{
var auth = host.Credentials.UserNameAuthentication;
auth.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
auth.CustomUserNamePasswordValidator = container.Resolve<UserNamePassValidator>();
};
This works perfectly, but it would have been nicer to be able to do it from the web.config. If anyone knows a better way please post :)
I came across a page on MSDN explaining transaction in WCF Services here. I tweaked the binding settings and used netTcpBinding. Here is the serviceModel section of my app.config file:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTcpBindingConfiguration1" transactionFlow="true">
<security mode="Message" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="OrderingService.OrderService">
<clear />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"
listenUriMode="Explicit">
</endpoint>
<endpoint address="net.tcp://localhost:8880/OrderingService"
binding="netTcpBinding" bindingConfiguration="netTcpBindingConfiguration1"
contract="OrderingService.IOrderService" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8888/OrderingService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
I created a windows application as the client of the service. I used netstat command to see the TCP connections between the client and the service (hosted in a console application). I realized for each operation (which was a button click in my client app that places a new order by invoking the methods of the service's proxy class), a new connection is created and all previous connections still remain ESTABLISHED. Obviously, this is not an ideal condition. I wondered what I did wrong and what setting or configuration would work out this problem by reducing the number of connections to only one. By the way, the service class that implements the service interface has InstanceContextMode set to PerSession. Here are the contract interface and the service class:
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IOrderService
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.NotAllowed)]
List<Customer> GetCustomers();
[OperationContract]
[TransactionFlow(TransactionFlowOption.NotAllowed)]
List<Product> GetProducts();
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string PlaceOrder(Order order);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string AdjustInventory(int productId, int quantity);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string AdjustBalance(int customerId, decimal amount);
}
[ServiceBehavior(TransactionIsolationLevel = IsolationLevel.Serializable,
TransactionTimeout = "00:00:20",
InstanceContextMode = InstanceContextMode.PerSession,
TransactionAutoCompleteOnSessionClose = true)]
public class OrderService : IOrderService
{...}
Here is the code the uses the proxy class in the client app:
using (TransactionScope scope = new TransactionScope())
{
try
{
proxy = new OrderServiceClient("NetTcpBinding_IOrderService");
result = proxy.PlaceOrder(order);
MessageBox.Show(result);
result = proxy.AdjustInventory(product.ProductId, quantity);
MessageBox.Show(result);
result = proxy.AdjustBalance(customer.CustomerId, product.Price * quantity);
MessageBox.Show(result);
proxy.Close();
scope.Complete();
}
catch (Exception exc)
{
MessageBox.Show("Error occurred: " + exc.Message);
}
}
With regards to the TCP connection remaining ESTABLISHED - are you calling .Close() on your instance of the client when you are finished with it?
If you want to use a single connection you should change the instance context mode to 'Single' and reuse the connection you establish in the client to process all your service calls. This suits an architecture where you want to maintain state within your service.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service : IService
{
}
I found this link very helpful when I was learning about context modes in WCF: CodeProject link
As you are currently using PerSession context mode you should be able to limit it to a single connection by adding a setting for maxConcurrentSessions in your behaviors section. You can do it like this:
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
<serviceThrottling maxConcurrentSessions="1" />
</behavior>
</serviceBehaviors>
This would only be a good idea if you have a single client.