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.
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 would like an example or explanation of how to connect a client to a wcf service when the client is not on the domain.
I imagine there is a way to specify domain credentials with the client and the wcf service could talk to the authority (dc) to see if the client is secure.
I followed the examples on the msdn and can connect to see the metadata (methods available) but when using wshttpbinding I get "An unsecured or incorrectly secured fault was received from the other party".
Thanks in advance!
By default, wsHttpBinding will use Windows credentials - this only works if both your service and your calling client are member of the same domain (or member of domains with a mutual trust relationship).
If you want to authenticate using username/password, there's a number of things you need to do:
the service needs a certificate to authenticate itself to the caller, and to provide an encryption mechanism for the exchange of username/passwords and messages. So you will need to create a security certificate and install it on the server machine, and configure it:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Internet">
<serviceCredentials
findValue="MyServiceCertificate"
storeLocation="LocalMachine"
storeName="My"
X509FindType="FindBySubjectName" />
</behavior>
<serviceBehaviors>
<behaviors>
<services>
<service name="MyService" behaviorConfiguration="Internet">
......
</service>
</services>
</system.serviceModel>
the client needs to set up a config that defines wsHttpBinding with message security, and username/password client credentials
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="UserNameWS">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
<wsHttpBinding>
<bindings>
<client>
<endpoint name="Default"
address="........."
binding="wsHttpBinding" bindingConfiguration="UserNameWS"
contract="........." />
</client>
</system.serviceModel>
on the server side, you need to set up a mechanism to authenticate those username/passwords - typically, the easiest way is to use the ASP.NET membership system
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Internet">
<userNameAuthentication
userNamePasswordValidationMode="MembershipProvider" />
<serviceCredentials
.....
</system.serviceModel>
before each call from the client, you need to set the username/password on your client-side proxy (this is one of the few things you cannot do in config - works only in code).
proxy.ClientCredentials.UserName.UserName = "YourUserName";
proxy.ClientCredentials.UserName.Password = "Top$Secret";
Read all about WCF security at the WCF Security Guidance site on Codeplex.
The error message "An unsecured or incorrectly secured fault was received from the other party" is a rather misleading one. A common cause is a difference in the bindings configuration between the client and the server. Check the system.serviceModel section of the web.config at the service side, and modify your client settings to match.
The reason why you can access metadata and cannot call service is that you are using WsHttpBinding probably with default configuration. It uses message security wich is involved only for service usage - not service metadata. It uses Windows credentials and Windows security to encrypt and sign message. Because of Windows security it works only when both client and server are on the same domain.
Your client is not part of domain - you can send windows credentials either with message security or transport security. In case of message security you will have to use clientCredentialType="UserName", default password validator and you will have to configure X509 certificate in service behavior to support encryption and signing. In case of transport security will either use HTTPS (X509 certificate configured in http.sys/IIS) or TransportCredentialOnly mode which will send windows user name and password as a plain text over HTTP (this is bad solution). In case of transport security set clientCredentialType="Basic".
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.
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.
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.