Restrict WCF Web Service functionality based on User Group - wcf

I have a WCF Web Service which is consuming by C# client application. I’m also having 4 groups stored in Active Directory. Client application is passing user credentials to connect this web service.
Web service exposing multiple APIs or Methods to be accessed by Client application as follows:
[OperationContract]
bool Read();
[OperationContract]
bool Write();
Read() method should be accessible for all clients
Write() method should be accessible by only users those belongs to specifc windows user group maintained by Active Directory.
Question:
How can we filter or restrict an exposed interface or method by client based on its user group maintain in AD?
jrista,
Thanks for your reply. I tried the same directives as PrincipalPermission as follows:
[PrincipalPermission(SecurityAction.Demand, Role = "Readers")]
[OperationContract]
bool Read();
[PrincipalPermission(SecurityAction.Demand, Role = "Writers")]
[OperationContract]
bool Write();
But it does not work. Read group user is also able to call the Writer() method and Writer group user is also able to call the Write() method.
One thing I want to tell you is that I'm using BasicHttpBind in my web.config file as follows:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBind">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" proxyCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="DXDirectory.DXDirectoryService" behaviorConfiguration="DXDirectory.Service1Behavior">
<!-- Service Endpoints -->
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpBind"
name="BasicBinding" contract="DXDirectory.IDXDirectoryService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DXDirectory.Service1Behavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above 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" />
<serviceAuthorization principalPermissionMode="UseWindowsGroups"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Is it required to implement wsHttpBinding for this functionality? If yes, then how can I implement wsHttpBinding in my Web Service?

I am not sure off the top of my head how to integrate AD credentials into the normal .NET security framework. However, it is possible (I'll see if I can find some links), and once you do, you should be able to use the standard security attribute to check for a "role", which would correspond to your AD group:
[OperationContract]
bool Read();
[PrincipalPermission(SecurityAction.Demand, Role = "Writers")]
[OperationContract]
bool Write();
To utilize AD groups, configure a service behavior:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<adServiceBehavior>
<serviceAuthorization principalPermissionMode="UseWindowsGroups" />
</adServiceBehavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Had another thought. Sometimes the desire is to not even have the Write() method on the interface at all. With WCF, you can implement multiple service contract interfaces on a single service class. An ideal solution might be to create two service contract interfaces, one with Read() and Write(), one with just Read(). Depending on the user logged into the client, you could use the Read() interface for those who only have read access, and the Read()/Write() interface for those with access to both. This would also allow you to expose the safest service contract to clients that shouldn't have write access, while utilizing the read/write contract internally for administrative purposes. You never expose code that could be potentially exploited this way.

jrista is right - you can use the built-in Windows authorization services including the "PrincipalPermission" attribute to limit access.
BUT: before you can authorize, you need to authenticate. First you need to know who's knocking on your service's door before deciding whether to let him (or her) in or not.
In order to do that, you need to make sure to use Windows credentials on your message exchange, and client and server must be in the same domain (or in domains with a mutual trust relationship). Also, you'll need to use a binding like wsHttp or netTcp that allows and supports Windows credentials by default, and you need to make sure to use and configure a binding security configuration that transports the Windows credentials across from the client to the server.
You'll need to have something like:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="Secured">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
and then you'll need to reference that binding configuration from your client and server endpoints.
WsHttpBinding and NetTcpBinding both default to using Windows client credentials, so out of the box, unless you've turned security off completely, you should get Windows credentials support in those two bindings.
Marc
PS:
As jrista shows (and I did in a previous answer to almost the same question you had), you really only need to add that PrincipalPermission attribute to the methods you want to limit to users who belong to a certain group - no manual messing around with AD group memberships etc. needed.
If you really must get the groups the user calling your service belongs to, you can check out the ".Groups" property of the WindowsIdentity calling:
WindowsIdentity winCaller = ServiceSecurityContext.Current.WindowsIdentity;
foreach(var group in winCaller.Groups)
{
Console.WriteLine(group.Value);
}
If you need the name of the user calling in, use winCaller.Name. If you need the SID for the user calling, use winCaller.User. It's all right there - no messing, no complicated code - just use it! :-)

Try Adding the Principalpermission attribute on the method in service class not on the operation contract in the service interface.

Related

How to authenticate wsdl get with TransportWithMessageCredential security mode for the endpoint?

I have a WCF endpoint that exposes a API with a basicHttpBinding. This biding is set to use security mode TransportWithMessageCredentialand UserName for clientCredentialType.
Because security is implemented at message level, at the WCF, the IIS needs to allow anonymous access. And so, wsdl can be obtain without providing any credentials.
How to force authentication to get the service metadata?
Here the current service configuration looks like (from web.config)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="secure">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="secure" name="someProject.MyService">
<endpoint binding="basicHttpBinding" contract="someProject.IService" bindingConfiguration="secure" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="secure">
<serviceMetadata httpsGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
I try the obvious, to set a specific binding for the metatada, by using service behavior configuration:
<behavior name="secure">
<serviceMetadata httpsGetEnabled="true" httpsGetBinding="basicHttpBinding" httpsGetBindingConfiguration="transportSecure" />
</behavior>
//and add the new binding
<basicHttpBinding>
<binding name="transportSecure">
<security mode="Transport">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
But it is not supported. It throws this:
MessageVersion 'Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
AddressingNone
(http://schemas.microsoft.com/ws/2005/05/addressing/none)' is not
supported in this scenario. Only MessageVersion 'EnvelopeNone
(http://schemas.microsoft.com/ws/2005/05/envelope/none) AddressingNone
(http://schemas.microsoft.com/ws/2005/05/addressing/none)' is
supported.
I don't understand this error or how to get around it.
Normally we will not disclose our metadata in the production environment,But if you want to enable metadata, we can use https binding to protect the metadata.
1.Configure a port with an appropriate X.509 certificate. The certificate must come from a trusted authority, and it must have an intended use of "Service Authorization." You must use the HttpCfg.exe tool to attach the certificate to the port.
2.Create a new instance of the ServiceMetadataBehavior class.
3.Set the HttpsGetEnabled property of the ServiceMetadataBehavior class to true.
4.Set the HttpsGetUrl property to an appropriate URL. Note that if you specify an absolute address, the URL must begin with the scheme https://. If you specify a relative address, you must supply an HTTPS base address for your service host. If this property is not set, the default address is "", or directly at the HTTPS base address for the service.
5.Add the instance to the behaviors collection that the Behaviors property of the ServiceDescription class returns, as shown in the following code.
ServiceMetadataBehavior sb = new ServiceMetadataBehavior();
sb.HttpsGetEnabled = true;
sb.HttpsGetUrl = new Uri("https://myMachineName:8036/myEndpoint");
myServiceHost.Description.Behaviors.Add(sb);
myServiceHost.Open();
This is authentication enabled on WCF, you can also enable windows authentication on IIS, both methods can protect metadata.
But in the production environment, I do not recommend that you enable metadata, because this will lead to the risk of metadata leakage.The call of WCF service can also be called through the channel factory. In this case, we can call WCF service without knowing the metadata of the server.
For more information on how to protect metadata, you can refer to this link:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-secure-metadata-endpoints?redirectedfrom=MSDN

How do I configure a WCF web service for Basic authentication, but expose metadata with anonymous authentication

I have a WCF SOAP-1.2 web service hosted in IIS that is using HTTP Basic Auth via a customBinding specification. In dev environments, it uses only HTTP. In QA, it uses HTTP and HTTPS. In prod, it uses HTTPS transport only.
Right now the WSDL is exposed by a serviceBehavior tag, rather simply, like this (using httpsGetEnabled as appropriate):
<serviceMetadata httpGetEnabled="true"/>
I would like to enable anonymous access to the WSDL/schemas only, as they currently require Basic Auth as does the actual service. How does one do that? I've dug around on MSDN, and found some resources pointing to use of a webHttpBinding for the metadata specifically, but I can't seem to get it to forget about Basic Auth:
<serviceMetadata httpGetEnabled="true" httpGetBinding="webHttpBinding" httpGetBindingConfiguration="metadatabinding" />
...
<bindings>
<webHttpBinding>
<binding name="metadatabinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</webHttpBinding>
...
</bindings>
In using the above tweaked metadata tags, I am prompted for Basic credentials in the browser when pulling up http://someserver/service.svc?wsdl (and those names have been sanitized).
In IIS, I have enabled anonymous and Basic auth for the site/application, such that the bindings ultimately control the credential requirements.
Oops, I actually did not change the IIS application configuration as I stated that I did in the question. To make the second, expanded web.config above work properly, you need to enable Anonymous and Basic Auth inside of IIS in the "Authentication" section of either a site or application so that at the application level, both are available. By using a binding for the actual service which has an authenticationScheme="Basic", the service is authenticated while the metadata is not.
I'm surprised this is not as directly documented; most helpful tips that I could find on other social sites or SO has suggested using a separate application or static resources for WSDLs and schemas, as opposed to the loosening access to the WCF generated metadata.
The authenticationScheme attribute change did the trick for me as well (from #Greg's answer).
However, I have a self-hosted service, so I added it to the App.config file instead.
This defines both HTTPS and Basic Authentication to the serviceMetaData endpoint:
<behaviors>
<serviceBehaviors>
<behavior name="HttpsAndBasicAuthentication" >
<serviceMetadata httpsGetEnabled="true" httpsGetUrl="https://localhost:8000/CalculatorService" />
<serviceAuthenticationManager authenticationSchemes="Basic"/>
</behavior>
</serviceBehaviors>
</behaviors>
Note that this behavior has to be referenced in the <service> element using the behaviorConfiguration attribute.

Combine old WCF service with new WCF service Almost There

I have a WCF service on IIS that a few .net web applications are using. I was tasked with writing a new WCF service, with the requirement that the existing web apps could use the new service without changing anything but their web.config.
So my new service needs 2 interfaces, I think? I've done that, I have three interfaces - ILocationsWCF (same name as the interface in the old service) ILocationDW (has new methods) and
ILocationService : ILocationsWCF, ILocationDW.
Then public class LocationService : ILocationService. I can write a new web app that uses ILocationService just fine - but I can't figure out how to make this new service have 2 endpoints, one for the old apps and one for the new ones (doing this because the old service is a bit awkward so I would like to keep them separated, then redeploy the old apps with the new service if the opportunity arises). Mostly, this change is driven by new source data - but I digress.
Here is the error I get:
A binding instance has already been associated to listen URI http://localhost:10737/LocationService.svc. If two endpoints want to share the same ListenUri, they must also share the same binding object instance. The two conflicting endpoints were either specified in AddServiceEndpoint() calls, in a config file, or a combination of AddServiceEndpoint() and config.
My attempt at web.config service model:
<system.serviceModel>
<services>
<service name="PPS.Services.Location.LocationService" behaviorConfiguration="LocationServiceBehavior">
<endpoint address=""
binding="basicHttpBinding" name="PPS.Services.Location.LocationService"
bindingNamespace="PPS.Services.Location"
contract="PPS.Services.Location.ILocationService"
bindingConfiguration="BasicHttpBinding_ILocationService"
behaviorConfiguration="HttpBehavior">
</endpoint>
<endpoint address=""
binding="basicHttpBinding" name="PPS.Services.Location.LocationsWCF"
bindingNamespace="PPS.Services.Location"
contract="PPS.Services.Location.ILocationsWCF"
bindingConfiguration="BasicHttpBinding_ILocationsWCF"
behaviorConfiguration="HttpBehavior">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="LocationServiceBehavior">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="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" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="HttpBehavior" />
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ILocationService" receiveTimeout="00:05:00" sendTimeout="00:05:00" maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"></binding>
<binding name="BasicHttpBinding_ILocationsWCF" receiveTimeout="00:05:00" sendTimeout="00:05:00" maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"></binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
My Interfaces:
namespace PPS.Services.Location
{
[ServiceContract(Name = "LocationService")]
public interface ILocationService : ILocationsWCF, ILocationServiceDW
{...
namespace PPS.Services.Location
{
[ServiceContract(Name = "LocationsWCF")]
public interface ILocationsWCF
{...
namespace PPS.Services.Location
{
[ServiceContract(Name = "LocationServiceDW")]
public interface ILocationServiceDW
{...
Any help with these endpoints, or have I gone off in the wrong direction?
EDIT -- NEW PROBLEM!
Thanks for the help, marc_s got me over that hump. Now, my goal is to replace the existing service with the new service, by changing the endpoint in web.config only. I cannot get this to work, I get the error like:
...cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher...
If I remove the old service from the application and replace it with the new one, then compile and run it works - but I don't want to have to re-deploy all my old apps, I would rather just replace the endpoint in the web.config. Can I even do this? There are differences in the 2 services, mainly a new database (our student data is now with a new vendor -- out of my control) plus I've learned a lot and was able to write a much better service.
Can I do what I want here, or will I need to run 2 services until I can move all the old apps to the new service?
Note, when I'm certain the contracts etc are identical, but if you need to see files just let me know which ones.
thanks.
One endpoint = one contract. If you've combined your two sets of service methods into a single service contract (ILocationService), you cannot have two separate endpoints.
What you should do is have one service implementation class (LocationService) that implements the two interfaces:
public class LocationService : ILocationsWCF, ILocationDW
Now, you have one service implementation, but you can define two separate endpoints:
<services>
<!-- the name= must exactly match the name of the concrete service implementation class -->
<service name="PPS.Services.Location.LocationService"
behaviorConfiguration="LocationServiceBehavior">
<!-- the contract= must exactly match the name of an existing service contract -->
<endpoint name="PPS.Services.Location.LocationService"
address=""
behaviorConfiguration="HttpBehavior">
binding="basicHttpBinding" bindingNamespace="PPS.Services.Location"
bindingConfiguration="BasicHttpBinding_ILocationService"
contract="PPS.Services.Location.LocationServiceDW" />
<!-- the contract= must exactly match the name of an existing service contract -->
<endpoint name="PPS.Services.Location.LocationsWCF"
address="someother"
behaviorConfiguration="HttpBehavior"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ILocationsWCF"
bindingNamespace="PPS.Services.Location"
contract="PPS.Services.Location.ILocationsWCF" />
</service>
</services>
Now you have two endpoints - each one exposing one service contract - and mind you: they have to have different address=..... values! You cannot have two different endpoints on the same address

Stand alone authentication wcf web service

Is there any issue with having a stand alone authentication WCF web service that takes a username and password? Should I use https or some other best practices?
Also if I want to reset the password of a user with a WCF web service, how can I prevent a brute force attack of any sort? or is there a best practice around this approach also? I would just send an email/username to reset the password.
Yes you should use https. Because authentication over http is equally prone to attack as an unauthenticated service, anyone can sniff the plain text passing through the wire. There is a good way in WCF you can implement authentication, use UserNamePasswordValidator class.. you can create some text-file on server so have the username-password that can be change by sending an email etc..
public class MyCustomValidator: System.IdentityModel.Selectors.UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
else
{
bool isValid = UserRepository.ValidateUser(userName,password); //any db based checking
if(!isValid )
{
throw new SecurityTokenException("Unknown Username or Password");
}
}
}
}
config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="CustomAuthentication">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CustomValidator">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyAssembly.MyCustomValidator, MyAssembly"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="MyService" behaviorConfiguration="CustomValidator">
<endpoint address="" binding="wsHttpBinding" contract="IMyService" bindingConfiguration="CustomAuthentication" />
</service>
</services>
</system.serviceModel>
if you wish to use authentication without https you can try this:
WCF Authentication using basicHttpBinding and custom UserNamePasswordValidator
This is a sensible approach if you want to externalise the authentication of your services, for example to support identity federation with another identity provider such as Active Directory Federation Services or even Facebook or Google using the Windows Azure Access Control Service.
An additional benefit of this is that it would be relatively easy to support alternative authentication schemes in the future, such as X.509 certificates.
It may seem over complicated for your needs but you should definitely consider implementing your authentication service using a standard protocol such as WS-Trust. Your service in this case would be a Security Token Service (STS) to use the jargon. This is fairly well supported using Windows Identity Foundation (WIF) and in fact the WIF tools for visual studio include a sample STS to get you going.
Alternatively, there is an excellent ready made, open source STS created by Dominic Baier, that you can download, customise if necessary (e.g. to use your own username/password store). You can download this here
http://identityserver.codeplex.com/
As I say, this is perhaps more complex than you need, but could be a really good investment in the future.

HttpContext is null in WCF service running in ASP.NET Compatibility Mode

I have a asp.net website that is hosting a WCF service. This service is then accessed from a desktop app. In my service the HttpContext is always null during the execution of the Validate method in my implementation of the UserNamePasswordValidator class. I'm using Username as the client credential type. I need access to the http context in order to get the Url the service was accessed from in order to validate the username and password correctly as the site can be accessed using different Urls and each one has a different user store.
The following attribute on the class that contains the method that will be called after the validator class (and the validator class as well)
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
I have a service is configured as follows:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpSecurityOptions">
<security mode="Message">
<message clientCredentialType="UserName" establishSecurityContext="true" negotiateServiceCredential="true"/>
<transport clientCredentialType="Certificate" proxyCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="SecurityServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFServer.MyAuthenticator" includeWindowsGroups="false"/>
<serviceCertificate findValue="myurl.com" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="SecurityServiceBehavior" name="Test.WCF.Actions">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpSecurityOptions" contract="WCFServer.IActions"/>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
I've seen the HttpContext is not initialised on first call bug but this happens to me for every call I make to the service, even when I call the same method on the same connection more than once
Edit: clarified question to answer marc_s's comment and Aliostad's question
Edit: Added following links that suggest the http context should not be null
http://blogs.msdn.com/b/wenlong/archive/2006/01/23/516041.aspx
http://msdn.microsoft.com/en-us/library/aa702682(v=VS.90).aspx
Can anyone lend me a hand with this please? I'd rather not have to put the site's Url in the appSettings config section for all my sites.
The problem is that you want to access HttpContext from Validate method. As I understand internal WCF implementation Validate method runs in different thread. By design this thread doesn't have access to any context available to main thread processing the request. In Validate method you can't access any WCF based context (OperationContext, ServiceSecurityContext, etc.) so I think it will be the same with HttpContext.
UserNamePasswordValidator's validate method is executed before asp.net pipeline is initialized. So the HttpContext is null. Try using OperationContext instead.
I am not clear on what you are trying to do.
aspNetCompatibilityEnabled only makes sense - as far as I know - when you are using new WCF REST API which does not require a binding configuration. Binding in WCF REST is managed by ASP.NET MVC routing.
If you use configuration API to set up a classic binding, then you are not using the new feature hence "no aspNetCompatibilityEnabled for you"!
So finally I thought of a workaround. I pass the url that the service is running in to the UserNamePasswordValidator.Validate though the username parameter. I use the format $username$|$siteurl$. Then at the server I separate the two. One thing to note is the ServiceSecurityContext.Current.PrimaryIdentity.Name property will then contain $username$|$siteurl$ for the rest of the request so you have to split it into its component everytime you want to access it.
Just to clarify why I need to do this. Our system can run multiple sites with different urls on the same home directory, each with separate authentication that is tied to the url. So without the url I can't authenticate the request. I had been using an appSetting key to provide the url but that meant each site had to have its own home directory.