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.
Related
I have a WCF service created with VS2013 on Win8. If I start the service via VS (localhost:port) I'm able to do GET response in json
but same service i am hosted on server (IIS7) then i get 404 Error
localhost URL : http://localhost:43596/abc.svc/LoginUser/abc/abc
Live URL: http://mywwebsite.com:80/abc.svc/LoginUser/abc/abc
In my opionio, there might something amiss with the hosting environment, try to enable WCF feature support in IIS.
Here is my service, wish it is useful to you.
IService1.cs
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet]
string GetData(int value);
Service1.svc.
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
Web.config
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior>
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="mybinding">
<security mode="Transport">
<transport clientCredentialType="None"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<protocolMapping>
<add binding="webHttpBinding" scheme="http"/>
<add binding="webHttpBinding" scheme="https" bindingConfiguration="mybinding"/>
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Since the above configuration support both http and https protocol, we need to add http and https binding in IIS site binding module.
Reference.
How to make WCF Service Use HTTPS protocol
Feel free to let me know if there is anything I can help with.
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");
}
}
}
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 am attempting to secure a new .Net 4.5 WCF service using HTTPS / SSL, Basic client credentials and the WebHttpBinding. From reading up online I found a good series of Blog Posts from Allen Conway which I have used as a template.
WCF configuration
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webInteropSecureBinding" allowCookies="false" maxBufferPoolSize="2097152" maxBufferSize="2097152" maxReceivedMessageSize="2097152">
<security mode="Transport">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="PsmDataProvider.PsmProvider" behaviorConfiguration="SecureRest">
<clear />
<endpoint address="" binding="webHttpBinding" bindingConfiguration="webInteropSecureBinding" name="PsmProvider" contract="PsmDataProvider.IPsmProvider" behaviorConfiguration="webHttpBehavior" />
<endpoint address="mex" binding="mexHttpsBinding" name="mex" contract="IMetadataExchange" listenUriMode="Explicit" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:44300/PsmProvider/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureRest">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="PsmDataProvider.Security.CustomerUserNamePasswordValidator, PsmDataProvider"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
CustomerUserNamePasswordValidator
I have stubbed out the CustomerUserNamePasswordValidator implementation and have confirmed that the constructor is called before the exception is raised.
using System;
using System.IdentityModel.Selectors;
namespace PsmDataProvider.Security
{
internal class CustomerUserNamePasswordValidator : UserNamePasswordValidator, ICustomerUserNamePasswordValidator
{
public CustomerUserNamePasswordValidator()
{
}
public override void Validate(string userName, string password)
{
if (userName == null) throw new ArgumentNullException("userName","The username must be provided in the request to access this service");
if (password == null) throw new ArgumentNullException("password", "The password must be provided in the request to access this service");
}
}
}
When I try to run the code in VS2012 through IIS Express the service fails to start with the below error.
If I remove the clientCredentialType from the configuration then it works but I require the additional security of using the username / password validation on the service and possibly at a method level in the future.
Is this something I have configured incorrectly in the WCF config or a problem with the configuration in IISExpress?
Please help...
The issue appears to be when using Basic Authentication when hosting the service in IIS as IIS wants to handle the authentication.
This is discussed in this MSDN blog post
In the version of WCF that shipped with .Net Framework 3.0 we didn't
support custom validators with transport level HTTP security. We
received much feedback from the community that this was a highly
desired feature, so I'm happy to say we added support for this
scenario in the 3.5 release of the .Net Framework. Note that this is
only supported under self hosted services.
There is a resolution as discussed in Allen Conway's Blog Post by implementing a custom authorisation manager derived from ServiceAuthorizationManager
CustomAuthorizationManager
public class CustomAuthorizationManager : ServiceAuthorizationManager
{
private const string UserName = "username";
private const string Password = "password";
protected override bool CheckAccessCore(OperationContext operationContext)
{
string authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if ((authHeader != null) && (authHeader != string.Empty))
{
string[] svcCredentials = System.Text.ASCIIEncoding.ASCII
.GetString(Convert.FromBase64String(authHeader.Substring(6)))
.Split(':');
var user = new { Name = svcCredentials[0], Password = svcCredentials[1] };
if ((user.Name.Equals(UserName) && user.Password.Equals(Password)))
return true;
else
return false;
}
else
{
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"PsmProvider\"");
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
}
Config
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webInteropSecureBinding" allowCookies="false" maxBufferPoolSize="51200" maxBufferSize="51200" maxReceivedMessageSize="51200">
<security mode="Transport"/>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="PsmDataProvider.PsmProvider" behaviorConfiguration="SecureRest">
<clear />
<endpoint binding="webHttpBinding" bindingConfiguration="webInteropSecureBinding"
name="PsmProvider" contract="PsmDataProvider.IPsmProvider" behaviorConfiguration="webHttpBehavior" />
<endpoint address="mex" binding="mexHttpsBinding" name="mex" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:44300/PsmProvider/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureRest">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization serviceAuthorizationManagerType="PsmDataProvider.Security.CustomAuthorizationManager, PsmDataProvider"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Note
Also note a comment from Travich regarding the IIS / IIS Express configuration
Travich said... One thing to help other users. It was briefly stated,
but something I overlooked... Turn off Basic Auth in IIS and remove
tag from your webHttpBinding!
Works for me.
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.