WCF web service custom authorization - wcf

I have WCF webservice using windows authentication and custom ServiceAuthorizationManager. Everything works fine, but if overridden CheckAccessCore returns false, I get error 500, instead of 401 as I expected. Service does not implement any service level error handling. How can I send 401 instead of 500 header?
Service config:
<!-- App configuration-->
<system.web>
<compilation debug="true" targetFramework="4.0" />
<customErrors mode="Off" />
</system.web>
<appSettings>
<!-- Allowed users divided by comma -->
<add key="allowedUsers" value="DOMAIN\User1, DOMAIN\User2" />
</appSettings>
<!--Webservice-->
<system.serviceModel>
<services>
<service name="WebService.ApiService">
<endpoint binding="basicHttpBinding" bindingConfiguration="AuthenticatedBinding" bindingNamespace="http://namespace.com/customapi" contract="WebService.IApiService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceAuthorization serviceAuthorizationManagerType="WebService.Model.Services.AuthorizationService, WebService" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="AuthenticatedBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
Custom authorization manager:
class AuthorizationService : ServiceAuthorizationManager
{
private List<string> allowedUsers = new List<string>();
public AuthorizationService() : base()
{
Configure();
}
protected override bool CheckAccessCore(OperationContext operationContext)
{
base.CheckAccessCore(operationContext);
return allowedUsers.Contains(operationContext.ServiceSecurityContext.WindowsIdentity.Name);
}
private void Configure()
{
var configRow = ConfigurationManager.AppSettings["allowedUsers"];
var parts = configRow.Split(',');
if (parts.Length > 0)
{
foreach (var part in parts)
allowedUsers.Add(part.Trim());
}
}
}
Result image:

I found on the web that error code 500 is the proper way how to send SOAP fault response. So everything is fine with my webservice (I am getting 'Access denied' fault with error code 500).
SOAP specification about it
Summary on Martin Karpiseks blog

Related

How to call WCF Rest service with Basic Auth using Postman?

I have created a WCF service with Basic Authentication following this article https://learn.microsoft.com/en-gb/dotnet/framework/wcf/feature-details/transport-security-with-basic-authentication.
But when I call it using Postman, I get an error (400 Bad Request).
If I use a small application written in C# setting out ClientCredentials, it works well.
Here is my server code:
public class DataService : IDataService
{
public bool GetData()
{
return true;
}
}
[ServiceContract]
public interface IDataService
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "GetData")]
bool GetData();
}
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.6.2" />
<httpRuntime targetFramework="4.6.2"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<bindings>
<wsHttpBinding>
<binding name="UsernameWithTransport">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="BasicAuthenticationTest">
<endpoint
address=""
binding="wsHttpBinding"
bindingConfiguration="UsernameWithTransport"
name="BasicEndpoint"
contract="BasicAuthTest.IDataService"/>
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
I also turned off "SSL certificate verification" and proxy in Postman

WCF Service calls error out with message Could not establish secure channel for SSL/TLS with authority

Hello I am trying to call a web service:-
https://example.com/Dealio/DealioCapLinkSvc.svc (actual name hidden).
I am able to browse this service on the browser. However when I call the service through my client application I get the following error:-
Could not establish secure channel for SSL/TLS with authority 'example.com'.
Can any one tell me what is going on ?
Below is App Config:-
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="SOAPEndPoint1" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://example.com/dealio/DealioCapLinkSvc.svc/soap"
binding="basicHttpBinding" bindingConfiguration="SOAPEndPoint1"
behaviorConfiguration="authBehavior"
contract="DealioService.IDealioLib" name="SOAPEndPoint1" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
<!-- Security Behavior -->
<endpointBehaviors>
<behavior name="authBehavior">
<authBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="authBehavior" type="CanadaDealio.AuthBehavior, CanadaDealio , Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
</system.serviceModel>
</configuration>
This is the code that calls the service:-
static void Main(string[] args)
{
Deal deal = new Deal();
deal = PopulateDealDetails(deal);
DealReturnResults dealioReturnResults = null;
DealioLibClient dealioServiceProxy = new DealioLibClient();
try
{
ValidationErrorList validationErrorList =
dealioServiceProxy.ValidateDealDetails(deal);
}
catch(Exception e)
{
}
}
Can anyone tell me what is going on ? I don't think this should be very hard to call a WCF Service.
If you look at the Microsoft Documentation, they have the below example that shows how to connect basichttpbinding to SSL
<system.serviceModel>
<services>
<service
type="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="Binding1"
contract="Microsoft.ServiceModel.Samples.ICalculator" />
</service>
</services>
<bindings>
<basicHttpBinding>
<!-- Configure basicHttpBinding with Transport security -- >
<!-- mode and clientCredentialType set to None.-->
<binding name="Binding1">
<security mode="Transport">
<transport clientCredentialType="None"
proxyCredentialType="None">
<extendedProtectionPolicy
policyEnforcement="WhenSupported"
protectionScenario="TransportSelected">
<customServiceNames></customServiceNames>
</extendedProtectionPolicy>
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
try to add the following code snippets when invoke the web service via adding the service reference.
Client-side.
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
try
{
var result = client.SayHello();
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Feel free to let me know if the problem still exists.

Custom UserNamePasswordValidator not called

I am trying to set up a WCF webservice with BasicHttpBinding and authentication using username/password. I made custom authentication class. However, it is never called (verified by debugging). This is my web.config:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<behaviors>
<serviceBehaviors>
<behavior>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Compareware_WebApp.webservices.AuthenticationValidator, Compareware_WebApp" />
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding">
<security mode="TransportCredentialOnly">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="Compareware_WebApp.webservices.Accounts">
<endpoint address="/webservices/Accounts.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding" name="BasicEndpoint"
contract="Compareware_WebApp.webservices.IAccounts" />
</service>
</services>
<client />
This is my authentication class:
namespace Compareware_WebApp.webservices
{
public class AuthenticationValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!Membership.ValidateUser(userName, password))
{
// This throws an informative fault to the client.
throw new FaultException("Unknown Username or Incorrect Password");
}
}
}
}
What am I doing wrong?
I also stuck in this problem, And I got the solutions. If you want to call the Validate method of the UserNamePasswordValidator So, you must be have to use the TransportWithMessageCredential security.
NOTE: You must be have to host the WCF Services at the IIS server and have to enable the SSL for you website otherwise it will be not work. If we use the TransportWithMessageCredential than we have to enable the SSL for the our website.
WCF Server Web.config file
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttp">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="TransportWithMessageCredential.Service1" behaviorConfiguration="wsHttpBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttp" contract="TransportWithMessageCredential.IService1">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="https://localhost:8080/WCFDemo"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="wsHttpBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="TransportWithMessageCredential.ServiceAuthanticator, TransportWithMessageCredential"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<system.web>
<compilation debug="true"/>
</system.web>
</configuration>
Service Authentication class
public class ServiceAuthanticator : UserNamePasswordValidator
{
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
public override void Validate(string userName, string password)
{
if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(password))
{
throw new FaultException("Please, Provide the username and password!!!");
}
if (userName != "abc" || password != "abc")
{
throw new FaultException("Sorry, Invalid username or password!!!");
}
}
}
WCF Client app.config file
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService1">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://kalpesh-pc/WCFAuth/Service1.svc" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1"
name="WSHttpBinding_IService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
Client Program to call the WCF Services
try
{
Service1Client objClient = new Service1Client();
objClient.ClientCredentials.UserName.UserName = "abc";
objClient.ClientCredentials.UserName.Password = "abc";
objClient.Open();
string strData = objClient.GetData(10);
}
catch (FaultException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
Happy Coding...
:)
Transport, as far as I understand, requires HTTPS to encrypt credentials and throws an exception if there is no SSL. TransportCredentialOnly will send the credentials in plain text and unencrypted and is recommended for testing ONLY.
Therefore, your credentials are send via transport. So <Transport> tag must be adjusted, not <Message>.
The following works for me:
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>

Custom X509CertificateValidator with configuration?

I'm setting up client certificates on my wcf service. All works great. The service requires client certs, my client test app supplies the cert and is able to make a request to one of the service end points.
No I want to implement a custom validator. I created a new class inheriting from X509CertificateValidator, and set it up in the services web config. I can put a breakpoint in the validate method and see it gets called. Awesome possum.
Now I want to be able to supply custom configuration parameters to the validator. The X509CertificateValidator has a LoadCustomConfiguration method which I can override, but it doesn't get called, I'm assuming it's because I'm not supplying any actual custom configuration anywhere - if that assumption is correct, how do I define my custom configuration parameters? Or is there some other way I should be doing this?
public class CustomValidator : System.IdentityModel.Selectors.X509CertificateValidator
{
/// <summary>
/// If the passed certificate is not valid according to the validation logic, this method throws a SecurityTokenValidationException. If the certificate is valid, the method returns to the caller.
/// </summary>
/// <param name="certificate"></param>
public override void Validate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate)
{
bool bValid = true;
// Check that there is a certificate.
if (certificate == null)
{
throw new ArgumentNullException("certificate", "Certificate was not supplied.");
}
bValid = certificate.Verify() &&
DateTime.Now <= certificate.NotAfter &&
DateTime.Now >= certificate.NotBefore;
if (!bValid)
{
throw new System.IdentityModel.Tokens.SecurityTokenValidationException("Certificate is not valid.");
}
}
public override void LoadCustomConfiguration(System.Xml.XmlNodeList nodelist)
{
base.LoadCustomConfiguration(nodelist);
}
}
Configuration
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<services>
<service name="WCFTransportAuthCertificateCustomValidation.Service1"
behaviorConfiguration="MapClientCertificates">
<endpoint binding="basicHttpBinding"
bindingConfiguration="TransportCertificateAuthentication"
contract="WCFTransportAuthCertificateCustomValidation.IService1">
</endpoint>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="TransportCertificateAuthentication">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
<behavior name="MapClientCertificates">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="X509CertificateValidation.CustomValidator, X509CertificateValidation" />
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https"/>
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>

Https error in WCF Web Api REST service

I've a windows xp machine with IIS 5.1 and I've signed the Default Web Site with a self created certificate. Now I've a REST service built using WCF Web API running under the default web site and when I try to invoke a particular resource I'm getting the followinng error.
The provided URI scheme 'https' is invalid; expected 'http'.
Parameter name: context.ListenUriBaseAddress
This is my configuration,
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule,
System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<services>
<service name="QuartzResource">
<endpoint binding="webHttpBinding" contract="webHttpBinding"
behaviorConfiguration="webHttp" bindingConfiguration="bindConfig"/>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="bindConfig">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="webHttp">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
This is the resource class,
[ServiceContract]
public class QuartzResource
{
[WebGet(UriTemplate = "")]
public List<Job> GetJobs()
{
...
return allJobs;
}
}
This is my Global.asax.cs,
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapServiceRoute<QuartzResource>("");
}
Note: Things are working fine when runs in VS dev server.