I am trying to code a WCF service with both REST and SOAP endpoints. I was initially using "TransportCredentialOnly" for the SOAP endpoints. As I started to add REST endpoints...I am using a third party OAUTH 1.0 class to provide security to the REST Service.
With the "TransportCredentialOnly" authentication, I had to enable "Windows Authentication" on the IIS Website Application.
The issue I am having is that the REST calls come back with a "Authentication failed" as IIS is expecting an initial authentication to happen with Windows Credentials before hitting the REST Endpoint.
I enabled "Anonymous Authentication" on the IIS application but still be prompted for Windows Credentials before proceeding.
Is there anyway to keep the "Windows Authentication" scheme for SOAP calls and have Anonymous authentication on the REST endpoints (which will proceed to use OAuth 1.0)? I don't really want to separate this out into two projects/services as some of the functions/methods/classes are universal between the SOAP and REST calls.
Here is my web config so far of attempts:
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
<authentication mode="Windows"/>
<authorization>
<allow roles="DOMAIN\Security_Group"/>
<deny users="*"/>
</authorization>
<customErrors mode="Off"/>
</system.web>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpEndpointBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</basicHttpBinding>
<webHttpBinding>
<binding name="webHttpBindingWithJsonP" />
</webHttpBinding>
</bindings>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="Anonymous">
<security mode="None"/>
</standardEndpoint>
</webHttpEndpoint>
</standardEndpoints>
<services>
<service name="service name">
<endpoint address="SOAP"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpEndpointBinding"
contract="contract name">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="REST"
kind="webHttpEndpoint"
binding ="webHttpBinding"
bindingConfiguration="webHttpBindingWithJsonP"
endpointConfiguration="Anonymous"
behaviorConfiguration="restBehavior"
contract ="contract name">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="service url"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="restBehavior">
<webHttp helpEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<useRequestHeadersForMetadataAddress/>
</behavior>
</serviceBehaviors>
</behaviors>
<!--<protocolMapping>
<add binding="basicHttpsBinding" scheme="http" />
</protocolMapping>-->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="false" />
</system.serviceModel>
<system.webServer>
<httpProtocol>
<customHeaders>
<!--<add name="Access-Control-Allow-Origin" value="*"/>
<add name="Access-Control-Allow-Methods" value="POST,GET"/>
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept"/>-->
</customHeaders>
</httpProtocol>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
10-20-2015 Update: I applied a new config to use serviceAuthorization in the web.config file
<service name="service name" behaviorConfiguration="Oauth">
<endpoint address="service address"
binding ="webHttpBinding"
behaviorConfiguration="restBehavior"
contract ="service contract">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="base address"/>
</baseAddresses>
</host>
</service>
<behavior name="Oauth">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceAuthorization serviceAuthorizationManagerType="Assembly.OAuthAuthorizationManager,Assembly" />
</behavior>
Here is the OAuthorizationManager Class:
public class OAuthAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
bool Authenticated = false;
string normalizedUrl;
string normalizedRequestParameters;
base.CheckAccessCore(operationContext);
// to get the httpmethod
HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)(operationContext.RequestContext.RequestMessage).Properties[HttpRequestMessageProperty.Name];
string httpmethod = requestProperty.Method;
// HttpContext.Current is null, so forget about it
// HttpContext context = HttpContext.Current;
NameValueCollection pa = HttpUtility.ParseQueryString(operationContext.IncomingMessageProperties.Via.Query);
if (pa != null && pa["oauth_consumer_key"] != null)
{
// to get uri without oauth parameters
string uri = operationContext.IncomingMessageProperties
.Via.OriginalString.Replace
(operationContext.IncomingMessageProperties
.Via.Query, "");
string consumersecret = "secret";
OAuthBase oauth = new OAuthBase();
string hash = oauth.GenerateSignature(
new Uri(uri),
pa["oauth_consumer_key"],
consumersecret,
null, // totken
null, //token secret
httpmethod,
pa["oauth_timestamp"],
pa["oauth_nonce"],
out normalizedUrl,
out normalizedRequestParameters
);
Authenticated = pa["oauth_signature"] == hash;
}
return Authenticated;
}
}
There is a workable solution to have a REST based WCF service hosted in IIS can use its own custom Basic Authentication. You can easily send back a response header to challenge for Basic Authentication credentials, and just have IIS wired up to "Anonymous Authentication." In this case IIS will only take care of Hosting and that's it.
Create a custom Authorization manager class that inherits from ServiceAuthorizationManager and configure this to your service.
public class RestAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
//Extract the Authorization header, and parse out the credentials converting the Base64 string:
var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if ((authHeader != null) && (authHeader != string.Empty))
{
var svcCredentials = System.Text.ASCIIEncoding.ASCII
.GetString(Convert.FromBase64String(authHeader.Substring(6)))
.Split(':');
var user = new { Name = svcCredentials[0], Password = svcCredentials[1] };
if ((user.Name == "user1" && user.Password == "test"))
{
//User is authrized and originating call will proceed
return true;
}
else
{
//not authorized
return false;
}
}
else
{
//No authorization header was provided, so challenge the client to provide before proceeding:
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"MyWCFService\"");
//Throw an exception with the associated HTTP status code equivalent to HTTP status 401
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
}
Add the above custom authorization manager to configuration:
<serviceBehaviors>
<behavior name="SecureRESTSvcTestBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<serviceAuthorization serviceAuthorizationManagerType="WcfRestAuthentication.Services.Api.RestAuthorizationManager, WcfRestAuthentication"/>
</behavior>
</serviceBehaviors>
Apply above behavior to only REST End points. Now IIS won't have control of any authorization here anymore. Make sure you use SSL certificate to secure the creds from being sent as plain text.
Related
I want to add some security to my WCF application service. I figured out how to add username/password authentication:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Changer.Service.Validation.ServiceAuthenticator, Changer.Service"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="MyBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="Changer.Service.Request.RequestService" behaviorConfiguration="MyBehavior">
<endpoint address="/" binding="wsHttpBinding" contract="Changer.Service.Request.IRequestService" bindingConfiguration="MyBinding" />
</service>
</services>
</system.serviceModel>
This is my custom data validation:
public class ServiceAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
// Check the user name and password
if (userName != Authentication.Providers.Service.PasswordChanger.UserName ||
password != Authentication.Providers.Service.PasswordChanger.Password)
{
throw new System.IdentityModel.Tokens.SecurityTokenException("Unknown username or password.");
}
}
}
Unfortunelly I am getting error which is because I have no valid certificate. I tried following this tutorial:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-an-iis-hosted-wcf-service-with-ssl
But without success. It says, that certificate hosts is not matching site url I am visiting. On client side I am getting error:
Could not establish trust relationship for the SSL/TLS secure channel with authority 'foo'. The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure
I can solve this by adding to my client app:
System.Net.ServicePointManager.ServerCertificateValidationCallback += delegate { return true; };
Which is basically not solving my problem.. What can I do with that? I just want to have simple user/pass authentication.
I decided to get rid off SSL, then my code changed to:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="PasswordChanger.Service.Validation.ServiceAuthenticator, PasswordChanger.Service"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name ="NewBinding">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
And after that I got this error:
Error: Cannot obtain Metadata from http://localhost:53705/R.svc If this is a Windows (R) Communication Foundation service to which you have access, please check that you have enabled metadata publishing at the specified address. For help enabling metadata publishing, please refer to the MSDN documentation at www.WS-Metadata Exchange Error URI: http://localhost:53705/R.svc Metadata contains a reference that cannot be resolved: 'http://localhost:53705/R.svc'. Content Type application/soap+xml; charset=utf-8 was not supported by service http://localhost:53705/R.svc. The client and service bindings may be mismatched. The remote server returned an error: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..HTTP GET Error URI: http://localhost:53705/R.svc The HTML document does not contain Web service discovery information.
So I decided to add services tag to my web.config next to bindings
<services>
<service name="PasswordChanger.Service.Request.RequestService" behaviorConfiguration="MyBehavior">
<endpoint address="/" binding="wsHttpBinding" contract="PasswordChanger.Service.Request.IRequestService" bindingConfiguration="NewBinding" />
</service>
And I got another error:
The service certificate is not provided. Specify a service certificate in ServiceCredentials.
Whether we use the message security or transport layer security mode, we all need to provide a certificate to ensure that the username/password authentication mode is secure.
I have made an example related transport security mode. we need provide a certificate to ensure that the service is hosted successfully.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding>
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<protocolMapping>
<add binding="wsHttpBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
If we don’t have a certificate, we could generate a self-signed certificate by using IIS Server Certificate Tool.
then we add https binding in the IIS website binding module so that the WCF service is hosted successfully.
Custom Authentication class. Based on the actual situation, configure this authentication class in the configuration file above.
internal class CustUserNamePasswordVal : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "jack" || password != "123456")
{
throw new Exception("Username/Password is not correct");
}
}
}
<serviceCredentials>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
Client.
//for validating the server certificate.
ServicePointManager.ServerCertificateValidationCallback += delegate
{
return true;
};
ServiceReference2.Service1Client client = new ServiceReference2.Service1Client();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
if we use the message security, we could set up the certificate by the following code.( Configure your actual certificate according to the actual situation)
<serviceCredentials>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" findValue="869f82bd848519ff8f35cbb6b667b34274c8dcfe"/>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
Refer to the below link.
WCF-TransportWithMessageCredential The HTTP request is unauthorized with client authentication scheme 'Anonymous'
WCF UserName & Password validation using wshttpbinding notworking
Feel free to let me know if there is anything I can help with.
I am new to WCF Service authentication, I was trying to achieve wcfauthentication using wshttpbinding. but i am getting below exception.
Could not find a base address that matches scheme https for the endpoint with binding WSHttpBinding. Registered base address schemes are [http].
Web.Config:
<?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>
<bindings>
<wsHttpBinding>
<binding name="wsHttp">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="WCFAuth.Service1" behaviorConfiguration="wsHttpBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttp" contract="WCFAuth.IService1">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:64765/"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="wsHttpBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFAuth.ServiceAuthanticator, WCFAuth"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
Service Authentication class:
using System;
using System.Collections.Generic;
using System.IdentityModel.Selectors;
using System.Linq;
using System.ServiceModel;
using System.Web;
namespace WCFAuth
{
public class ServiceAuthanticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
string AppUserName = "ABC";
string AppPwd = "abc";
try
{
if (userName.ToLower() != AppUserName.ToLower() && password != AppPwd)
{
throw new FaultException("Unknown Username or Incorrect Password");
}
}
catch (Exception ex)
{
throw new FaultException("Unknown Username or Incorrect Password");
}
}
}
}
Client Side config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<!--<binding name="base" />-->
<binding name="base">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:64765/Service1.svc" binding="basicHttpBinding"
bindingConfiguration="base" contract="WCFAuth.IService1" name="base" />
</client>
</system.serviceModel>
</configuration>
Consumer:
class Program
{
static void Main(string[] args)
{
try
{
WCFAuth.Service1Client client = new WCFAuth.Service1Client();
client.ClientCredentials.UserName.UserName = "test";
client.ClientCredentials.UserName.Password = "test";
var temp = client.GetData(1);
Console.WriteLine(temp);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
I am getting attached exception when i try to browser svc file.
Can someone correct me, where i am committing mistake, thanks in advance.
The problem here is that you are using a WSHttpBinding with Transport Security, but the base address you set is http. It is not possible to work with http here, because you are sending credentials over the wire.
Either change it to https, or create a second binding configuration for development purposes. One with Transport Security (https), and a second without (http).
Also make sure that your clients binding matches the binding from your server.
As Marc mentioned, we are supposed to provide a certificate when hosting the service. there might be something amiss during the process of hosting the service.
Here is a reference configuration, wish it is useful to you.
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttp">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="WCFAuth.Service1" behaviorConfiguration="wsHttpBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttp" contract="WCFAuth.IService1">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="wsHttpBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFAuth.ServiceAuthanticator, WCFAuth"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Then we should add a https binding in IIS Site Bindings module.
The service address would be https://x.x.x.x:8865/Service1.svc
One thing must be noted that we should trust the service certificate when we call the service by adding service reference.
ServicePointManager.ServerCertificateValidationCallback += delegate
{
return true;
};
ServiceReference2.Service1Client client = new ServiceReference2.Service1Client();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
Besides, if we use SecurityMode.Message, we are supposed to provide a certificate in code snippets.
<serviceCredentials>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" findValue="869f82bd848519ff8f35cbb6b667b34274c8dcfe"/>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
Feel free to let me know if there is anything I can help with.
I want to enable "Basic" Authentication to my service. Disabled anonymous and windows Auth. And enabled "basicAuthentication" in applicationhost.config, but im not able access the service. Getting below error.
HTTP Error 401.2 - Unauthorized You are not authorized to view this page due to invalid authentication headers.
I am using IIS Express 10. Here is my applicationhost.config
<authentication>
<anonymousAuthentication enabled="false" userName="" />
<basicAuthentication enabled="true" />
<clientCertificateMappingAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="false">
</iisClientCertificateMappingAuthentication>
<windowsAuthentication enabled="false">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>
</authentication>
And here is my Web.config
<?xml version="1.0"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5.2">
<assemblies>
<add assembly="System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
</compilation>
<httpRuntime targetFramework="4.5.2"/>
</system.web>
<!--Enable directory browsing in the Server-->
<system.webServer>
<directoryBrowse enabled="true"/>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="MyWCFServices.DemoREST" behaviorConfiguration="DemoREST">
<!--Define base https base address-->
<host>
<baseAddresses>
<add baseAddress="https://localhost:44300/HelloWorldService.svc/"/>
</baseAddresses>
</host>
<!--webHttpBinding allows exposing service methods in a RESTful manner-->
<endpoint name="rest" address="" binding="webHttpBinding"
contract="MyWCFServices.IDemoREST" bindingConfiguration="webHttpTransportSecurity"
behaviorConfiguration="DemoREST"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DemoREST">
<!-- To avoid disclosing metadata information, set the value below to
false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="false" 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"/>
<!--Using custom username and Password-->
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyWCFServices.CustomUserNameValidator, HelloWorldService" />
</serviceCredentials>
<serviceAuthenticationManager authenticationSchemes="Basic"/>
</behavior>
</serviceBehaviors>
<!--Required default endpoint behavior when using webHttpBinding-->
<endpointBehaviors>
<behavior name="DemoREST">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
And Custom validator class:
namespace MyWCFServices
{
class CustomUserNameValidator : UserNamePasswordValidator
{
// This method validates users. It allows in two users, user1 and user2
// This code is for illustration purposes only and
// must not be used in a production environment because it is not secure.
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException("You must provide both the username and password to access this service");
}
if (!(userName == "user1" && password == "test") && !(userName == "user2" && password == "test"))
{
// This throws an informative fault to the client.
throw new FaultException("Unknown Username or Incorrect Password");
// When you do not want to throw an informative fault to the client,
// throw the following exception.
// throw new SecurityTokenException("Unknown Username or Incorrect Password");
}
}
}
}
Please guide me if I am wrong anywhere.
I have created custom ServiceAuthorizationManager as "CustomUserNamePasswordValidator" for my WCF project. below is the snippet of my project. I want my wcf to call up this authorication class before it actually start calling on WCF API but this is not happening. My "Login" WCF API is calling paraller to this authorization class. so when there is call by client to Login it calls both
"CustomUserNamePasswordValidator" and Login method simultaneously.
Authorization Class
public class CustomUserNamePasswordValidator : ServiceAuthorizationManager
{
HttpRequestMessageProperty httpProperties;
string operationName;
protected override bool CheckAccessCore(OperationContext operationContext)
{
operationName = GetOperationName(operationContext);
httpProperties = (HttpRequestMessageProperty)operationContext.IncomingMessageProperties["httpRequest"];
string authHeader = httpProperties.Headers[HttpRequestHeader.Authorization];
string subno = string.Empty;
string password = string.Empty;
string version = string.Empty;
string credntialType = string.Empty;
string[] credentials = authHeader.Split(':');
credntialType = credentials[0];
password = credentials[1];
if (!AuthorizeUser(password))
{
throw new ArgumentException("401:Token invalid or expired.(0x000)");
}
}
}
private int AuthenticateUser(string subno, string pin, string version)
{
}
}
WCF Client Service Snippet
public class ClientService : IClientService
{
public wsLoginResult LoginUser()
{
HttpRequestMessageProperty httpReqProps = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties["httpRequest"];
string res = httpReqProps.Headers[HttpRequestHeader.Authorization];
foreach (var item in res.Split(':'))
ActivityLog("Activity", "Login Steps", item, item);
}
}
Web.Config Snippet
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add name="wmas_subsConnectionString" connectionString="Data Source=WT;Initial Catalog=wmas;User ID=sa;Password=ra3?" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<compilation targetFramework="4.5" debug="true"/>
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<client>
<endpoint address="http://192.168.1.12:7002/MobileApplicationWS/MobileApplicationApiWSImplService"
binding="basicHttpBinding" bindingConfiguration="MobileApplicationApiWSPortBinding"
contract="VASService.MobileApplicationApiWS"
name="MobileApplicationApiWSPort" />
</client>
<services>
<service name="ClientService.ClientService" behaviorConfiguration="ClientService.ServiceBehavior">
<endpoint address=""
binding="webHttpBinding" bindingConfiguration="webHttpBindingConfiguration"
contract="ClientService.IClientService" behaviorConfiguration="webBehaviour"/>
<endpoint address="stream"
binding="webHttpBinding" bindingConfiguration="webHttpBindingConfigurationStreamed"
contract="ClientService.IClientService" behaviorConfiguration="webBehaviour"/>
<endpoint address="mex"
binding="mexHttpsBinding"
contract="IMetadataExchange" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="MobileApplicationApiWSPortBinding" />
</basicHttpBinding>
<webHttpBinding>
<binding name="webHttpBindingConfiguration" />
<binding name="webHttpBindingConfigurationStreamed" transferMode="StreamedResponse" />
</webHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="ClientService.ServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceAuthorization serviceAuthorizationManagerType="ClientService.CustomUserNamePasswordValidator, ClientService" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webBehaviour">
<webHttp/>
</behavior>
</endpointBehaviors>
</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>
The reason why your solution doesn't work is that CustomUserNamePasswordValidator can't be used in a RESTful service. Take a look at: http://msdn.microsoft.com/en-us/library/aa354513(v=vs.110).aspx
The example uses SOAP and defines the behaviour of the endpoint which activates serviceAuthorization tag. If you don't define the security of the endpoint the serviceAuthorization simply won't work.
<bindings>
<wsHttpBinding>
<!-- username binding -->
<binding name="Binding">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
In a RESTful service there is no SecurityMode = Message, there are only 3: None/Transport/TransportCredentialOnly. Read on about it here: http://msdn.microsoft.com/en-us/library/bb924478(v=vs.110).aspx
clientCredentialType="UserName" is only available in Message.
You can try defining the endpoint security mode to: Transport and then the credential type to: Basic/Certificate/Digest/None/Ntlm/Windows, but seeing as your solution is none of these not sure how well that will work.
There is (I think) a better way of doing authentication if you are extracting headers and not using any "approved" way. Try implementing an extension service: http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iparameterinspector(v=vs.110).aspx
Good Luck!
I want to secure a service application in WCF 4, using a selfsigned certificate (generated by inetmgr).
But, I can't. When I call a method of the service, I have a MessageSecurityException:
The HTTP request was forbidden with client authentication scheme 'Anonymous'.
The web.config file:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<customErrors mode="Off"/>
</system.web>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="TransportSecurity">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Certificate" />
<message clientCredentialType="Certificate"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="testingServiceBehavior">
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<services>
<service behaviorConfiguration="testingServiceBehavior"
name="Testing.Service1">
<endpoint address=""
binding="wsHttpBinding"
bindingConfiguration="TransportSecurity"
contract="Testing.IService1" />
<endpoint address="mex"
binding="mexHttpsBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
And the code where I trying to consume the service is:
public static bool validateCertificates(object sender,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors error)
{
return true;
}
private void button1_Click(object sender, EventArgs e)
{
System.Net.ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(validateCertificates);
WSHttpBinding binding = new WSHttpBinding();
binding.Name = "secureBinding";
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
EndpointAddress endpointAddress = new System.ServiceModel.EndpointAddress("https://rtsa.dnsalias.com:2490/Service1.svc");
ProCell2.Servicios.Informes.Service1Client client = new Servicios.Informes.Service1Client(binding, endpointAddress);
client.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindBySubjectName,
"ServerWeb2");
client.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindBySubjectName,
"ServerWeb2");
client.GetInformation(); // <-------- Here cause the exception
The SSL configuration:
Please add the following lines to your client code:
// Disable credential negotiation and the establishment of
// a security context.
myBinding.Security.Message.NegotiateServiceCredential = false;
myBinding.Security.Message.EstablishSecurityContext = false;
See http://msdn.microsoft.com/en-us/library/ms733102.aspx for more details and see What are the impacts of setting establishSecurityContext="False" if i use https? for the impact it has on your communication.