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.
Related
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.
I have a MVC application which uses forms authentication. This application also hosts a WCF webservice (the Model). Webservice caters c# objects to the application, and same data is available as JSON when called from outside the application (browser).
Everything is working fine apart from the fact that the Webservice is not authenticating any request. Following is the what I have in web.config:
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<behaviors>
<endpointBehaviors>
<behavior name="ServiceBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithJson" crossDomainScriptAccessEnabled="true" />
</webHttpBinding>
</bindings>
<services>
<service name="Services.MyService">
<endpoint address="http://localhost:1234/MyService.svc/" binding="webHttpBinding"
bindingConfiguration="webHttpBindingWithJson"
contract="Services.IService"
behaviorConfiguration="ServiceBehavior"/>
</service>
</services>
I would like to authenticate Webservice requests:
Requests from within the application should be authenticated automatically
When Webservice is called from outside the application, users are asked to get authenticated.
Any help would be appreciated.
/D
Sounds like you need an internal endpoint that serves a wsdl for the binding in to the website (authenticated using the ASP.NET identity the website is running under) and a separate binding externally for the JSON requests that uses something like OAuth to authenticate.
Either way you're looking at two different authentication mechanisms.
I'm currently investigating WCF for implementing a RESTful service that will run through IIS.
Currently our software offers the ability to authenticate users against a range of sources
Our own internal user accounts stored in the database
A specified windows active directory where if authentication is
successful, a lookup is done to find which internal account the
winows one is linked to
Another LDAP server e.g Novell
So the way I want this to work is that a client sends an http(s) request with an
authentication header (basic for now) over SSL, then the service will use custom
authentication to implement the process described above.
For the moment I am self-hosting the service and trying to get the custom authentication
example working, it starts up correctly but all I get when I try to make a request from a browser
or a tool where i can attach an authentication header is
"Error 101 (net::ERR_CONNECTION_RESET): The connection was reset."
I have set a breakpoint in the custom authentication class an it is never reached, so I'm guessing its a problem with the
configuration.
My app.config;
<configuration>
...
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="secure">
<security mode="Transport">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="CELCAT.RegisterMarker.RegisterMarker" behaviorConfiguration="myServiceBehavior">
<endpoint address="https://mymachine:8001/servicename"
binding="webHttpBinding"
bindingConfiguration="secure"
contract="myServiceContract" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="myServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
<serviceAuthorization serviceAuthorizationManagerType="MyServiceAuthorizationManager, authenticatonassembly" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="servicenamespace, serviceassembly" />
<serviceCertificate findValue="certname"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
I have read a post that said what I am trying to do is impossible out of the box with WCF
and to achieve this I will need to write a custom module or request interceptor as describe below;
authentication via custom module;
http://custombasicauth.codeplex.com/
authentication via request interceptor;
http://www.codeproject.com/KB/WCF/BasicAuthWCFRest.aspx
This seems like it should be possible to me, so my questions are
Is what i'm trying to do possible?
If so what have I got wrong? or if not which work around is best?
OK after much googling and prompting from Andrew Church (Thanks Andrew) I've figured this out.
The problem was although I had generated a certificate, I hadn't bound it to a port. Steps to help generate certificates and binding them can be found at;
http://www.codeproject.com/Articles/24027/SSL-with-Self-hosted-WCF-Service
This however asks you to use httpcfg, this tool doesn't exist on Windows Vista or 7 (my OS), so a further Google revealed this article;
http://msdn.microsoft.com/en-us/library/ms733791.aspx
Which tells me to use netsh, perfect! Well not quite, because this requires a parameter called appid, I didn't know where I could find this so a further search lead back here;
What appid should I use with netsh.exe?
So I followed all of the steps, commented out the certificate part of my app.config and hey presto I hit my break point in the custom config.
Hope this helps anyone else with the same problem
I am not sure whether this will work, but what I've done in the past is use a custom HTTP module. Our API uses access tokens, so we use the module to inspect headers for the presence of a token, if it doesn't exist we redirect to an Authentication endpoint in the api. The endpoint expects Basic authentication. Hope this helps.
A WIF-based WCF service needs to call method FederatedServiceCredentials.ConfigureServiceHost(), or put the equivalent element <federatedServiceHostConfiguration> in the web.config file, to work. This is a setting on the service level, in other words it applies for all endpoints.
According to the method documentation, the ServiceHostBase instance is modified in several WIF-specific ways. For example, the authorization is replaced by a WIF-based authorization class.
Now I'd like to have a single <service> (inside <system.serviceModel><services>) with multiple <endpoint>s, where one endpoint is WIF-based, and the others are using plain Windows authentication.
Update. In response to an answer below, let me explain why we want to mix WIF and non-WIF endpoints. If we only use WIF, then each of our customers needs an STS, like AD FS. Setting this up is not difficult, but it is a hurdle, especially if they just want to test drive our software. So what we do is install in a mode where Windows integrated authentication is used (for our web services, and also for our front end), and then later they can switch to a mode where AD FS is used.
So basically we want to be able to install without AD FS to lower the barrier to entry of our application.
To do this, the <service> needs a <federatedServiceHostConfiguration>. However -- and here is my problem -- this affects also the non-WIF endpoints for that same service: for example, they suddenly use the WIF authorization manager (an instance of class ClaimsAuthorizationManager).
So my question is: what is the recommended way to mix WIF and non-WIF endpoints in a single WCF <service>?
I don't think you can. In your situation though, you should only have the one WIF endpoint have leave the multiple credential support to the STS.
You can put multiple endpoints on your STS to handle different types of authentication. One for Windows, one for username/password for example.
I did a code camp oz session last year that demonstrated this. The source is attached to my blog post at http://www.neovolve.com/post/2010/11/21/CodeCampOz-Not-a-WIF-of-federation.aspx. Have a look at the web.config in NotAWif Demo\4 - Identity Delegation\NotAWif.DelegationSTS.
<system.serviceModel>
<services>
<service behaviorConfiguration="ServiceBehavior"
name="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract">
<endpoint address="UserName/IWSTrust13"
binding="ws2007HttpBinding"
bindingConfiguration="ws2007HttpBindingUserNameConfiguration"
contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract" />
<endpoint address="Windows/IWSTrust13"
binding="ws2007HttpBinding"
bindingConfiguration="ws2007HttpBindingWindowsConfiguration"
contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract" />
<endpoint address="mex"
binding="mexHttpsBinding"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost/NotAWif.DelegationSTS/Service.svc" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<ws2007HttpBinding>
<binding name="ws2007HttpBindingUserNameConfiguration">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="UserName"
establishSecurityContext="false" />
</security>
</binding>
<binding name="ws2007HttpBindingWindowsConfiguration">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="Windows"
establishSecurityContext="false" />
</security>
</binding>
</ws2007HttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<serviceCertificate findValue="DefaultApplicationCertificate"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
This is how I configured the STS to support multiple types of authentication. The RP should only deal in claims, not claims|WindowsIdentity. It is the STS's responsibility to convert a particular type of authentication into a set of claims that the RP will use.
You may be confusing the use of WIF with using an STS. They are not related.
WS2007FederationHttpBinding will cause the WCF endpoint to expect an issued token (from an STS).
WS2007HttpBinding or NetTcpBinding can require a Windows token.
You can use WIF to handle both, in fact it is WITH WIF that you are able to have a service behavior that supports two different token formats more effectively.
The issued token endpoint will rely on the configuration for the saml11/saml2 security token handler in WIF config to process the token and the trusted issuer section to establish trust of that token.
The windows endpoint will rely on one of the windows security token handlers to process the windows token.
Both will funnel through the WIF service authz manager but will hydrate claims for windows or for your issued token. you can use the claimsAUthenticationManager to transform those claims prior to reaching the claimsauthorizationmanager to authorize access.
THere are lots of ways to skin this cat but that is definitely possible.
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.