Why won't Authorization Rules in IIS restrict access to my WCF service? - wcf

I have a standalone WCF service hosted in IIS 10. I would like to restrict access to the web service to a select group of users. I was able to do this for a web application by doing the following in IIS:
Authentication: Windows Authentication only (disabled Anonymous Authentication)
Authorization Rules: Allow a predefined group (i.e., Roles)
However, when I do the above steps for the web service, and changed clientCredentialType="Windows" in its web.config, it still allows any user from the domain to talk to it. Am I missing something obvious? Do web services function differently than web applications in terms of configuring authorization? Given my setup I would expect only users in the MyTestGroup to be able to talk with the web service, and all others getting 401 - Unauthorized.
As an aside, I tried setting up "Deny Everyone" rules but domain users could still talk to the web service, so I feel like the Authorization settings aren't being effectuated somehow. Looking for any insight on this.
Here are the relevant web.config contents:
<system.serviceModel>
<services>
<service name="StudyManagement.StudyManagement">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="secureHttpBinding" name="StudyManagement" contract="StudyManagement.IStudyManagement" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<bindings>
<basicHttpBinding>
<binding maxReceivedMessageSize="1048576" />
<binding name="secureHttpBinding">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="false" minFreeMemoryPercentageToActivateService="0" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
<directoryBrowse enabled="false" />
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Allow" users="" roles="MyAllowGroup" />
</authorization>
</security>
</system.webServer>

You can refer to this website to re-create the authorization rules. In addition, the authorization of wcf services can use ServiceAuthenticationmanager, examples and tutorials.

The following Microsoft documentation helped to answer my question:
WCF services and ASP.NET
Important takeaways:
The ASP.NET HTTP runtime handles ASP.NET requests but does not participate in the processing of requests destined for WCF services, even though these services are hosted in the same AppDomain as is the ASP.NET content. Instead, the WCF Service Model intercepts messages addressed to WCF services and routes them through the WCF transport/channel stack.
Within an AppDomain, features implemented by the HTTP runtime apply to ASP.NET content but not to WCF. Many HTTP-specific features of the ASP.NET application platform do not apply to WCF Services hosted inside of an AppDomain that contains ASP.NET content. Examples of these features include the following:
File-based authorization: The WCF security model does not allow for the access control list (ACL) applied to the .svc file of the service when deciding if a service request is authorized.
Configuration-based URL Authorization: Similarly, the WCF security model does not adhere to any URL-based authorization rules specified in System.Web’s configuration element. These settings are ignored for WCF requests if a service resides in a URL space secured by ASP.NET’s URL authorization rules.
Solution:
Use ASP.NET Compatibility Mode by configuring web.config:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
Unlike the default side-by-side configuration, where the WCF hosting infrastructure intercepts WCF messages and routes them out of the HTTP pipeline, WCF services running in ASP.NET Compatibility Mode participate fully in the ASP.NET HTTP request lifecycle. In compatibility mode, WCF services use the HTTP pipeline through an IHttpHandler implementation, similar to the way requests for ASPX pages and ASMX Web services are handled. As a result, WCF behaves identically to ASMX with respect to the following ASP.NET features:
File-based authorization: WCF services running in ASP.NET compatibility mode can be secure by attaching file system access control lists (ACLs) to the service’s .svc file.
Configurable URL authorization: ASP.NET’s URL authorization rules are enforced for WCF requests when the WCF service is running in ASP.NET Compatibility Mode.
I recommend reading the entire article for additional information. It's a short and helpful read.
Thanks to #Shiraz Bhaiji for the article reference on WCF Authorization using IIS and ACLs.

Related

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.

WCF Service hosted in IIS 7 using basicHttpBinding and TransportCredentialOnly fails

I am trying to configure a basic IIS 7 hosted WCF service that uses Windows Authentication to authorize users. I have seen many examples that demonstrate how to flow credentials using basicHttpBinding with <security mode="TransportCredentialOnly"> and SSL. When I configure my service to use TransportCredentialOnly, I get the following error if I try to view the svc file in IE:
Could not find a base address that matches scheme http for the
endpoint with binding BasicHttpBinding. Registered base address
schemes are [https].
I am hosting in IIS 7. SSL is configured with a valid certificate. Windows Authentication is on. Anonymous authentication is off. Application pool is ASP.Net v4.0 running under the ApplicationPoolIdentity
Here is the config file for my service:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<authentication mode="Windows" />
<roleManager enabled="true" defaultProvider="AspNetWindowsTokenRoleProvider">
</roleManager>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="svcTest" >
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" httpsHelpPageEnabled="true" httpHelpPageEnabled="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpEndpointBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="WCF_Test.Service1" behaviorConfiguration="svcTest">
<endpoint name ="Service1Endpoint"
address="EndpointTest"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpEndpointBinding"
contract="WCF_Test.IService1">
</endpoint>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
If I change the binding to use Transport instead of TransportCredentialOnly then I am able to view my service file in IE. I can then create a proxy to my web client and call a method on the service from my client and attempt to authorize the user from the service method using this code:
if(System.Web.Security.Roles.IsUserInRole(#"Admins"))
This code does not work because it uses the identity of the account running IIS on the server (IIS APPPOOL\ASP.NET v4.0) and not that of the user calling the web service from a web page.
How do I configure IIS 7 with a valid SSL certificate to use basicHttpBinding with security mode="TransportCredentialOnly"?
How do I flow my users Windows credentials client to the web service so I can authorize users on the web service using this code?
[PrincipalPermission(SecurityAction.Demand, Role = "Admins")]
or this code
if(System.Web.Security.Roles.IsUserInRole(#"Admins"))
Any help would be greatly appreciated.
Thank You
Probably the problem is in the configuration of IIS, before it gets to your code or your web.config.
If IIS has anonymous authentication turned on, the request coming into ASP.net will look like it came from the user identity of IIS.
In the IIS config you must turn off anonymous authentication and turn on windows authentication.
My guess is you need to use the basicHttp s Binding instead.

jsonp with wcf works with vs.net server but NOT on IIS

This is driving me nuts. I have a .net 4, wcf service that is outputting jsonp. It works using the built in web server with vs.net however if i try to host in iis7 on windows 7 64bit i don't get any response.
If I try to navigate to svc file while hosted in iis7 I get
"Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service."
If trying to access via client jquery jsonp request i don't get any response from the service being hosted in iis7
So, the configuration of the service (web.config) is fine when hosted within vs.net web server (just doesn't work with iis)
Here is the config
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithJsonP" crossDomainScriptAccessEnabled="true" />
</webHttpBinding>
</bindings>
<services>
<service name="ServiceSite.CustomersService">
<endpoint address="" binding="webHttpBinding"
bindingConfiguration="webHttpBindingWithJsonP" contract="ServiceSite.CustomersService"
behaviorConfiguration="webHttpBehavior"/>
</service>
</services>
</system.serviceModel>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
The site must be ntlm/windows secured.
I added the following to the web.config bindings section
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm"/>
</security>
When browsing the svc file I now get Cross domain javascript callback is not supported in authenticated services
Really? Is this true jsonp is not supported?
This is probably symptomatic of misconfiguration of the web site. Go to Authntication feature of the website on the IIS Manager and make sure it is setup.
I'm pretty sure because of how JSONP cross domain works with today's shortcomings that you always have to implement your own custom security system.
I use SSL and HMAC security for JSONP cross domain authentication. I do the authentication myself on the service.
When doing JSONP you cannot set headers, or anything, on the request because it's done through adding tags, not an actual XMLHTTP request object.

Authenticating a WCF call from Silverlight using ADAM (AD LDS)

I have a Silverlight 4 client that invokes several WCF services. We want the communication to be encrypted using SSL (I have this part solved already) and that every call be authenticated against AD LDS (ADAM), do you have any simple example showing how to make this work? There's plenty of documentation on the oh-so-many WCF options but I haven't been able to find a simple working example of this particular (but I think very common) scenario (SSL encryption + ADAM authentication + Silverlight). Any help or pointers greatly appreciated.
You can use CustomUserNameValidator in WCF:
http://msdn.microsoft.com/en-us/library/aa702565.aspx
http://nayyeri.net/custom-username-and-password-authentication-in-wcf-3-5
and in Custom Validator's Validate Method you can query ADAM to authenticate user.
Regards.
Try this link on the Codeplex website, it seems like the setup and configuration for the scenario you've described. It provides a thorough checklist of all of the required settings:
Intranet – Web to Remote WCF Using Transport Security (Trusted Subsystem, HTTP)
If this isn't you exact scenario, take a look at the following section that may fill the gaps:
Application Scenarios (WCF Security)
The answer may depend on how you will handle permissioning, since you use ASP.net's membership provider for these functions.
If you want claims based authorization ADFS 1.0 (not 2.0) supports ADAM. If you want a STS that has more options try codplex's StarterSTS
If you want to use Role-Based Administration, try Enterprise Library from Microsoft P&P, the ASP.net membership provider, or direct COM access to Authorization manager (formerly known as AzMan)
I prefer & use the claims-based approach:
Use ActiveDirectory + ADFS 2.0
(why not use the built in fault tolerance and replication of AD?, ADAM is a pain)
Silverlight Implementation of Ws-Trust as documented here:
http://www.leastprivilege.com/UsingSilverlightToAccessWIFSecuredWCFServices.aspx
Edgar, I'm also interested in any results you had, I am at the same place you were in.
Shoaib, I have looked at this but I think it is less desirable than using just the .config via ActiveDirectoryMembershipProvider, as then you are just using off the shelf components, not writing your own security system.
EDIT:
I hope this helps someone. I can't belive there is not a good example of this on the internet. It is simple enough. As I said before this is superior to using a custom authentication system.
Using AD LDS (ADAM) authentication with Silverlight compatible WCF calls (non wsHttp)
Client side:
1) Invocations from Silverlight look like this, this works if you are using the Channel Factory too.
var client = new MyWCFServiceClient();
client.GetPersonCompleted += client_GetPersonCompleted;
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
client.GetPersonAsync();
2) return values from server will have an Error property if the login fails. If the user lookup fails, the error is something confusing like “at least one security token could not be validated”. Since your server side code is not able to re-wrap this (as it's all happening in the web.config) it is better for your client code to catch System.ServiceModel.Security.MessageSecurityException and interpret it as a login failure, or check the InnerException message to make sure it is the "security token" thing.
void client_GetPersonCompleted(object sender, GetPersonCompletedEventArgs e)
{
if (e.Error == null)
{
// do stuff with e.Result;
}
if (e.Error is MessageSecurityException)
{
// Your login did not work
}
}
Server side:
1) The WCF service class must have
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] or Required
2) you have to set up your LDS instance with SSL enabled, which is tricky. See: h't't'p://erlend.oftedal.no/blog/?blogid=7
3) web config - need to:
Add the LDS connection string
Add ActiveDirectoryMembershipProvider
Add <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
change your custom binding to include <security authenticationMode="UserNameOverTransport"/> see http://msdn.microsoft.com/en-us/library/dd833059(VS.95).aspx
Example:
<configuration>
<connectionStrings>
<add name="ADConnectionString" connectionString="LDAP://myserver:[SSL port number]/[where your user are in LDS, in my case: ‘OU=ADAM Users,O=Microsoft,C=US’]" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<membership defaultProvider="MyActiveDirectoryMembershipProvider">
<providers>
<add
name="MyActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="[domain]\[username]"
connectionPassword="[plain text windows password]"
connectionProtection="Secure"
/>
</providers>
</membership>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehaviour">
<serviceMetadata
httpsGetEnabled="true"
httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="MyActiveDirectoryMembershipProvider"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="myCustomBinding">
<security authenticationMode="UserNameOverTransport"/>
<!-- <binaryMessageEncoding /> this is optional, but good for performance-->
<httpsTransport />
</binding>
</customBinding>
</bindings>
<services>
<service name="MessageBasedSecurity.Web.MyWCFService" behaviorConfiguration="MyServiceBehaviour">
<endpoint address="" binding="customBinding" bindingConfiguration="myCustomBinding"
contract="MessageBasedSecurity.Web.MyWCFService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
I hope this helps someone. I welcome comments/improvements.

Using Windows Role authentication in the App.config with WCF

I am using a WCF service and a net.tcp endpoint with serviceAuthentication's principal PermissionMode set to UseWindowsGroups.
Currently in the implementation of the service i am using the PrincipalPermission attribute to set the role requirements for each method.
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public string method1()
I am trying to do pretty much the same exact thing, except have the configuration for the role set in the app.config. Is there any way to do this and still be using windows groups authentication?
Thanks
If you are hosting your WCF service in IIS, it will run in the ASP.NET worker process, which means you can configure authentication and authorization as you would do with ASMX web services:
<system.Web>
<authentication mode="Windows"/>
<authorization>
<allow roles=".\Administrators"/>
<deny users="*"/>
</authorization>
</system.Web>
Then you will have to disable anonymous access to your endpoint in IIS, and instead enable Windows Integrated Authentication.In the IIS management console you do that by bringing up the 'Properties' dialog for your virtual directory. You will then find the security settings in the 'Directory Security' tab.
Of course, the only communication channel available will be HTTP. Clients will have to provide their Windows identity in the request at the transport-level with these settings:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WindowsSecurity">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://localhost/myservice"
binding="wsHttpBinding"
bindingConfiguration="WindowsSecurity"
contract="IMyService" />
</client>
</system.serviceModel>
Note that if your service endpoint uses wsHttpBinding then you will also have to add SSL to your endpoint since that's a requirement enforced by WCF when you using transport-level security.
If you instead go for the basicHttpBinding, you are then able to use a less secure authentication mode available in WCF called TransportCredentialOnly, where SSL is no longer required.
For more detailed information, here is a good overview of the security infrastructure in WCF.
Lars Wilhelmsen has posted a solution for this problem. Have a look at
http://www.larswilhelmsen.com/2008/12/17/configurable-principalpermission-attribute/
If I understood well you want to select the role at runtime. This can be done with a permission demand within the WCF operation. E.g.
public string method1()
{
PrincipalPermission p = new PrincipalPermission(null, "Administrators");
p.Demand();
...