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! :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With