There are two communication paths in this sample. First is between the client and the façade service, the second is between the façade service and the backend service.
The client to the façade service communication path uses wsHttpBinding with a UserName client credential type. This means that the client uses username and password to authenticate to the façade service and the façade service uses X.509 certificate to authenticate to the client. The binding configuration looks like the following example.
<bindings>
<wsHttpBinding>
<binding name="Binding1">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
The façade service authenticates the caller using custom UserNamePasswordValidator implementation. For demonstration purposes, the authentication only ensures that the caller's username matches the presented password. In the real world situation, the user is probably authenticated using Active Directory or custom ASP.NET Membership provider. The validator implementation resides in FacadeService.cs file.
public class MyUserNamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
// check that username matches password
if (null == userName || userName != password)
{
Console.WriteLine("Invalid username or password");
throw new SecurityTokenValidationException(
"Invalid username or password");
}
}
}
The custom validator is configured to be used inside the serviceCredentials behavior in the façade service configuration file. This behavior is also used to configure the service's X.509 certificate.
<behaviors>
<serviceBehaviors>
<behavior name="FacadeServiceBehavior">
<!--The serviceCredentials behavior allows you to define -->
<!--a service certificate. -->
<!--A service certificate is used by the service to -->
<!--authenticate itself to its clients and to provide -->
<!--message protection. -->
<!--This configuration references the "localhost" -->
<!--certificate installed during the setup instructions. -->
<serviceCredentials>
<serviceCertificate
findValue="localhost"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"Microsoft.ServiceModel.Samples.MyUserNamePasswordValidator,
FacadeService"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
The façade service to the backend service communication path uses a customBinding that consists of several binding elements. This binding accomplishes two things. It authenticates the façade service and backend service to ensure that the communication is secure and is coming from a trusted source. Additionally, it also transmits the initial caller's identity inside the Username security token. In this case, only the initial caller's username is transmitted to the backend service, the password is not included in the message. This is because the backend service trusts the façade service to authenticate the caller before forwarding the request to it. Because the façade service authenticates itself to the backend service, the backend service can trust the information contained in the forwarded request.
The following is the binding configuration for this communication path.
<bindings>
<customBinding>
<binding name="ClientBinding">
<security authenticationMode="UserNameOverTransport"/>
<windowsStreamSecurity/>
<tcpTransport/>
</binding>
</customBinding>
</bindings>
The security binding element takes care of the initial caller's username transmission and extraction. The windowsStreamSecurity element and TcpTransport element take care of authenticating façade and backend services and message protection.
To forward the request, the façade service implementation must provide the initial caller's username so that WCF security infrastructure can place this into the forwarded message. The initial caller's username is provided in the façade service implementation by setting it in the ClientCredentials property on the client proxy instance that façade service uses to communicate with the backend service.
The following code shows how GetCallerIdentity method is implemented on the façade service. Other methods use the same pattern.
public string GetCallerIdentity()
{
CalculatorClient client = new CalculatorClient();
client.ClientCredentials.UserName.UserName = ServiceSecurityContext.Current.PrimaryIdentity.Name;
string result = client.GetCallerIdentity();
client.Close();
return result;
}
As shown in the previous code, the password is not set on the ClientCredentials property, only the username is set. WCF security infrastructure creates a username security token without a password in this case, which is exactly what is required in this scenario.
On the backend service, the information contained in the username security token must be authenticated. By default, WCF security attempts to map the user to a Windows account using the provided password. In this case, there is no password provided and the backend service is not required to authenticate the username because the authentication was already performed by the façade service. To implement this functionality in WCF, a custom UserNamePasswordValidator is provided that only enforces that a username is specified in the token and does not perform any additional authentication.
public class MyUserNamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
// Ignore the password because it is empty,
// we trust the facade service to authenticate the client.
// Accept the username information here so that the
// application gets access to it.
if (null == userName)
{
Console.WriteLine("Invalid username");
throw new
SecurityTokenValidationException("Invalid username");
}
}
}
The custom validator is configured to be used inside the serviceCredentials behavior in the façade service configuration file.
<behaviors>
<serviceBehaviors>
<behavior name="BackendServiceBehavior">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"Microsoft.ServiceModel.Samples.MyUserNamePasswordValidator,
BackendService"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
To extract the username information and information about the trusted façade service account, the backend service implementation uses the ServiceSecurityContext class. The following code shows how the GetCallerIdentity method is implemented.
public string GetCallerIdentity()
{
// Facade service is authenticated using Windows authentication.
//Its identity is accessible.
// On ServiceSecurityContext.Current.WindowsIdentity.
string facadeServiceIdentityName =
ServiceSecurityContext.Current.WindowsIdentity.Name;
// The client name is transmitted using Username authentication on
//the message level without the password
// using a supporting encrypted UserNameToken.
// Claims extracted from this supporting token are available in
// ServiceSecurityContext.Current.AuthorizationContext.ClaimSets
// collection.
string clientName = null;
foreach (ClaimSet claimSet in
ServiceSecurityContext.Current.AuthorizationContext.ClaimSets)
{
foreach (Claim claim in claimSet)
{
if (claim.ClaimType == ClaimTypes.Name &&
claim.Right == Rights.Identity)
{
clientName = (string)claim.Resource;
break;
}
}
}
if (clientName == null)
{
// In case there was no UserNameToken attached to the request.
// In the real world implementation the service should reject
// this request.
return "Anonymous caller via " + facadeServiceIdentityName;
}
return clientName + " via " + facadeServiceIdentityName;
}
The façade service account information is extracted using the ServiceSecurityContext.Current.WindowsIdentity property. To access the information about the initial caller the backend service uses the ServiceSecurityContext.Current.AuthorizationContext.ClaimSets property. It looks for an Identity claim with a type Name. This claim is automatically generated by WCF security infrastructure from the information contained in the Username security token.