I have created a Service with below Service contract
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
With below Custom Authentication (not in App_Code)
public class CustomAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
try
{
if (userName.Equals("user") && password.Equals("pass"))
{
//Good to go
}
}
catch (Exception ex)
{
throw new FaultException("Authentication failed");
}
}
}
My Config on Service. I used basicHttpBinding with Custom User Authentication (I don't want to use wsHttpBinding which makes it mandatory to have certificate)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="myBasicBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="TestService.Service1" behaviorConfiguration="customBehavior">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="myBasicBinding" contract="TestService.IService1" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="customBehavior">
<!-- 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="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="TestService.CustomAuthenticator,TestService"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
Service is hosted on IIS 8.5 with Anonymous and Forms Authentications enabled. I took a Console application client, added service reference and below config
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://techspider/Service/Service1.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
But, when I call a method on the service, it seems to be accepting any user name / password, which makes me think that it is not going to the custom authentication class I implemented.
If I don't supply any UserName, it is throwing me an error which indicates my setup is correct.
Service1Client client = new Service1Client();
client.GetData(1);
The username is not provided. Specify username in ClientCredentials.
The below code even after supplying wrong credentials, was able to fetch me the output.
Service1Client client = new Service1Client();
client.ClientCredentials.UserName.UserName = "aaaa";
client.ClientCredentials.UserName.Password = "dddd";
client.GetData(1);
Can anyone suggest if I'm missing any Config on Server? How do I ensure my CustomAuthenticator gets executed with every method call. I searched a number of questions online but none of them resolved.
The feeling that each and every username password combination will be accepted is right. That's the case because you're not really validating the credentials.
if (userName.Equals("user") && password.Equals("pass"))
{
//Good to go
}
If these demo-credentials are submitted, you're good to go ;-) But what if this doesn't apply? If any other username password combination is submitted, then nothing happens! And that's exactly the reason why your service is accepting arbitrary credentials. In the catch-block you're throwing an informative FaultException, but it won't be triggered as long as there doesn't occur an exception (which isn't the case in this simple example).
If you take a look at the docs, there's a very important remark and a corresponding code sample:
Override the Validate method to specify how the username and password is validated. If the username and password do not pass validation, then throw a SecurityTokenValidationException.
So instead of doing nothing if the submitted credentials don't match you should throw a SecurityTokenValidationException to reject the login-attempt.
public class CustomAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
try
{
if (userName.Equals("user") && password.Equals("pass"))
{
//Good to go
}
else
{
// add this line
throw new SecurityTokenException("Unknown Username or Password");
}
}
catch (Exception ex)
{
throw new FaultException("Authentication failed");
}
}
}
Related
I have configured a new Azure hosted WCF service with a custom username validator. The validator class will validate username and passwords from an existing aspnetUsers table in an Azure database.
I have configured the service using TransportWithMessageCredentials binding so the clients will be providing their usernames and passwords in clear text in the request.
My code will then look up the user and get their hashed password from the database and then use this to hash the password sent via the service. If they match then allow the request.
to validate the password I am using this code.
public static bool checkPassword(string hashedPassword, string password)
{
byte[] buffer4;
if (hashedPassword == null)
{
return false;
}
if (password == null)
{
throw new ArgumentNullException("password");
}
byte[] src = Convert.FromBase64String(hashedPassword);
if ((src.Length != 0x31) || (src[0] != 0))
{
return false;
}
byte[] dst = new byte[0x10];
Buffer.BlockCopy(src, 1, dst, 0, 0x10);
byte[] buffer3 = new byte[0x20];
Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
{
buffer4 = bytes.GetBytes(0x20);
}
return ByteArraysEqual(buffer3, buffer4);
}
So my question really is, Is sending the username and passwords in this way secure enough? As everything is going over https I am assuming it is but would like some guidance as I am fairly new to security in general.
The service will also be IP restricted.
Here is my service model config.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehaviour">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyValidatorClass,MyNameSpace" />
</serviceCredentials>
<!-- 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="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="HttpBinding" maxReceivedMessageSize="2097152" receiveTimeout="00:02:00" sendTimeout="00:02:00">
</binding>
<binding name="HttpsBinding" maxReceivedMessageSize="2097152" receiveTimeout="00:02:00" sendTimeout="00:02:00">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="MyService" behaviorConfiguration="MyServiceBehaviour">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="HttpsBinding" contract="MyContract" />
<host>
<baseAddresses>
<add baseAddress="https://MyServiceInAzure.net/" />
</baseAddresses>
</host>
</service>
</services>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
And here is what the clients will be using:
Client.Service call = new Client.ServiceClient();
call.ClientCredentials.UserName.UserName = "MyUsername";
call.ClientCredentials.UserName.Password = "MyPassword";
var result = call.PostCall("Hello World");
Thanks
So my question really is, Is sending the username and passwords in this way secure enough?
Yes. Since everything which send by HTTPS will be encrypted, you could send the user name and password without encrypted manually. Per my opinion, it is not recommend to send user name and password every time when sending request to your service. I suggest you generate a token at the first time and using the token to validate your request for a period of time.
I've been trying to figure this out for a couple of days. I know that I'm close but can't quite figure out what I'm missing.
The service is in a website hosted on IIS
Basic authentication is enabled for the site, and all others including anonymous authentication is disabled.
I have created a self signed certificate and set up a https binding
I have overridden the Validate method of the UsernamePasswordValidator but when I attach a break point it isn't reached. So my problem is likely something to do with this.
When I attempt to access my service method at https://localhost/Services/JobSiteService.svc/rest/get I am continually prompted for username and password as i receive a 401 unauthorized error.
Can anyone see what I'm doing wrong?
Appropriate code below:
Web.Config
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="Validity.WebService.JobSiteService" behaviorConfiguration="SecureRestBehavior">
<endpoint address="rest" binding="webHttpBinding" behaviorConfiguration="RESTBehavior" bindingConfiguration="webHttpTransportSecurity" contract="Validity.WebService.Contracts.IJobSiteService" />
<endpoint address="meta" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureRestBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Validity.WebService.IdentityValidator, Validity.WebService" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="RESTBehavior" >
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="webHttpBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
Service Contract:
[ServiceContract]
public interface IJobSiteService
{
[OperationContract]
[WebInvoke(Method="GET", ResponseFormat=WebMessageFormat.Json)]
List<JobSite> Get();
}
Custom Validator (it's in the namespace "Validity.WebService" but the code formatting broke when I included it.):
public override void Validate(string userName, string password)
{
using (var context = new ValidityContext())
{
using (var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context)))
{
var user = userManager.Find(userName, password);
if (user == null)
{
var msg = String.Format("Unknown Username {0} or incorrect password {1}", userName, password);
throw new FaultException(msg);//the client actually will receive MessageSecurityException. But if I throw MessageSecurityException, the runtime will give FaultException to client without clear message.
}
else
{
SessionOperationContext.Current.Items["CurrentUser"] = user;
}
}
}
}
Any help with this would be greatly appreciated.
Finally figured this out.
The solution was to disable basic authentication on the IIS server and override the CheckAccessCore method of the ServiceAuthorizationManager to validate the user.
I have a WCFservice that is a HTTPS REST API which exposes a few WebGet methods. I am using transport level security over HTTPS. This is all working fine. I implemented a custom UserNamePasswordValidator, overriding the Validate method as per MSDN documentation.
public override void Validate(string userName, string password)
{
WebFaultException rejectEx = new WebFaultException(HttpStatusCode.Unauthorized);
rejectEx.Data.Add("HttpStatusCode", rejectEx.StatusCode);
if (null == userName || null == password)
throw rejectEx;
if (userName == "someusername" && password == "somepassword") return;
throw rejectEx;
}
Accessing the service in a browser via HTTPS only (http is disabled), username and password are prompted for, leaving one out or entering one or both incorrectly results in the prompt coming back as I am throwing a 401.
The issue I have is that Validate is never called if both the username and password are left out when accessing the service. I need to be able to handle this and return an informative error, or even just a 401, to the browser/client.
Debugging shows that Validate is never fired when leaving out both the username and password. This means that in a browser you end up with a blank page and no errors.
How can I handle this situation and return a response to the client prompting for credentials again?
Edit to include config info:
<behaviors>
<serviceBehaviors>
<behavior name="RESTEndpointBehaviour">
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyAPI.CustomAuthenticator, MyAPI"
/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="RESTEndpointBehaviour" name="MyAPI.RestService">
<endpoint binding="webHttpBinding" bindingConfiguration="BasicHttpSSLBinding"
contract="MyAPI.IRestService" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:443" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="BasicHttpSSLBinding">
<security mode="Transport">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</webHttpBinding>
</bindings>
I ended up solving this puzzle by implementing a serviceAuthorization instead. As per the below blog article, with a few tweaks to match my application.
http://www.allenconway.net/2012/07/using-basic-authentication-in-rest.html
I'm not very experienced with WCF, as such I'm a little stuck with this one, I allow my WCF service to configure it's own endpoints (my manual attempts have been less than successful). Now this works fine appart from one issue, It adds a second endpoint using the local machine name, and the local windows domain.
My Config file is such:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<useRequestHeadersForMetadataAddress>
<defaultPorts>
<add scheme="https" port="443"/>
</defaultPorts>
</useRequestHeadersForMetadataAddress>
<!-- 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>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
However when adding this WCF to a WPF application, it generates the following config (Url changed for security)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IAdminService">
<security mode="None">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" />
</security>
</binding>
<binding name="BasicHttpsBinding_IAdminService">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://machinename.domain.local/PortalServices/AdminService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IAdminService"
contract="AdminService.IAdminService" name="BasicHttpBinding_IAdminService" />
<endpoint address="https://mysite.co.uk/PortalServices/AdminService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpsBinding_IAdminService"
contract="AdminService.IAdminService" name="BasicHttpsBinding_IAdminService" />
</client>
</system.serviceModel>
As you can see, it adds a working URL and a local domain URL
http://machinename.domain.local/PortalServices/AdminService.svc
How can i prevent this from being added? as it becomes a bit of a pain having to remember to manually remove it after every deploy/service update.
As Requested, this is one of the Interface classes.
[ServiceContract]
public interface IAdminService
{
[OperationContract]
List<PortalApp> GetApplications();
[OperationContract]
int AddApplication(string AppName, string Desc, string version, bool enabled);
[OperationContract]
bool EditApplication(int appid, string AppName, string Desc, string version, bool enabled);
[OperationContract]
bool AddAppAccess(int appid, Int16 uid);
[OperationContract]
bool RemAppAccess(int appid, Int16 uid);
[OperationContract]
List<PortalUser> GetUsers();
}
Ok, thanks to Jontatas mentioning the fact the endpoints have seperate binding types it got me thinking.
Turns out i had forgotten to remove the http binding from IIS itself (which is not used). Removing that binding from IIS also removed the incorrect binding in the generated config.
I've created a simple WCF Service which is hosted in IIS. Now i want to use my own userName authentication.
My web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="WcfIIsBasicAuthTest.Service1" behaviorConfiguration="MyBehavior">
<endpoint address=""
binding="basicHttpBinding"
contract="WcfIIsBasicAuthTest.IService1"
bindingConfiguration="MyBinding"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfIIsBasicAuthTest.MyValidator, WcfIIsBasicAuthTest"/>
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
The Validator
namespace WcfIIsBasicAuthTest
{
public class MyValidator :UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if(!(userName == "test" && password == "test"))
{
throw new SecurityTokenException("Validation Failed!");
}
}
}
}
If i start this WCF Service from within visual studio, i get the following error: The authentication schemes configured on the host ('Ntlm, Anonymous') do not allow those configured on the binding 'BasicHttpBinding' ('Basic').
If i try to connect to the service if it is hosted in IIS, i get error messages depending on which authentication type is set, but it doesn't work at all.
Error if only Anonymous authentication is enabled: The authentication schemes configured on the host ('Anonymous') do not allow those configured on the binding 'BasicHttpBinding' ('Basic').
If i set Basic Authentication in IIS, it demands a valid local user which i don't want to provide(since i want to write my own userprovider).
Any hints/links how to setup such a basic auth userprovider with wcf and iis?
Can you set the below configuration for using your own UserNameValidator:
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
For that to work with basicHttpBinding you would need to have HTTPS setup as WCF doesnt allow username password being passed over the channel in clear text. The other alternative is to use wsHttpBinding.