Walkthrough: Creating Custom Client and Service Credentials
This topic shows how to implement custom client and service credentials and how to use custom credentials from application code.
Credentials Extensibility Classes
The ClientCredentials and ServiceCredentials classes are the main entry points to the Windows Communication Foundation (WCF) security extensibility. These credentials classes provide the APIs that enable application code to set credentials information and to convert credential types into security tokens. (Security tokens are the form used to transmit credential information inside SOAP messages.) The responsibilities of these credentials classes can be divided into two areas:
Provide the APIs for applications to set credentials information.
Perform as a factory for SecurityTokenManager implementations.
The default implementations provided in WCF support the system-provided credential types and create a security token manager that is capable of handling those credentials types.
Reasons to Customize
There are multiple reasons for customizing either client or service credential classes. Foremost is the requirement to change the default WCF security behavior with regard to handling system-provided credential types, especially for the following reasons:
Changes that are not possible using other extensibility points.
Adding new credential types.
Adding new custom security token types.
This topic describes how to implement custom client and service credentials and how to use them from application code.
First in a Series
Creating a custom credentials class is only the first step, because the reason for customizing credentials is to change WCF behavior regarding credentials provisioning, security token serialization, or authentication. Other topics in this section describe how to create custom serializers and authenticators. In this regard, creating custom credential class is the first topic in the series. Subsequent actions (creating custom serializers and authenticators) can be done only after creating custom credentials. Additional topics that build upon this topic include:
Procedures
To implement custom client credentials
Define a new class derived from the ClientCredentials class.
Optional. Add new methods or properties for new credential types. If you do not add new credential types, skip this step. The following example adds a
CreditCardNumber
property.Override the CreateSecurityTokenManager method. This method is automatically called by WCF security infrastructure when the custom client credential is used. This method is responsible for creating and returning an instance of an implementation of the SecurityTokenManager class.
Important
It is important to note that the CreateSecurityTokenManager method is overridden to create a custom security token manager. The security token manager, derived from ClientCredentialsSecurityTokenManager, must return a custom security token provider, derived from SecurityTokenProvider, to create the actual security token. If you do not follow this pattern for creating security tokens, your application may function incorrectly when ChannelFactory objects are cached (which is the default behavior for WCF client proxies), potentially resulting in an elevation of privilege attack. The custom credential object is cached as part of the ChannelFactory. However, the custom SecurityTokenManager is created on every invocation, which mitigates the security threat as long as the token creation logic is placed in the SecurityTokenManager.
Override the CloneCore method.
public class MyClientCredentials : ClientCredentials { string creditCardNumber; public MyClientCredentials() { // Perform client credentials initialization. } protected MyClientCredentials(MyClientCredentials other) : base(other) { // Clone fields defined in this class. this.creditCardNumber = other.creditCardNumber; } public string CreditCardNumber { get { return this.creditCardNumber; } set { if (value == null) { throw new ArgumentNullException("value"); } this.creditCardNumber = value; } } public override SecurityTokenManager CreateSecurityTokenManager() { // Return your implementation of the SecurityTokenManager. return new MyClientCredentialsSecurityTokenManager(this); } protected override ClientCredentials CloneCore() { // Implement the cloning functionality. return new MyClientCredentials(this); } }
Public Class MyClientCredentials Inherits ClientCredentials Private creditCardNumberValue As String Public Sub New() End Sub ' Perform client credentials initialization. Protected Sub New(ByVal other As MyClientCredentials) MyBase.New(other) ' Clone fields defined in this class. Me.creditCardNumberValue = other.creditCardNumberValue End Sub Public Property CreditCardNumber() As String Get Return Me.creditCardNumberValue End Get Set If value Is Nothing Then Throw New ArgumentNullException("value") End If Me.creditCardNumberValue = value End Set End Property Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager ' Return your implementation of the SecurityTokenManager. Return New MyClientCredentialsSecurityTokenManager(Me) End Function Protected Overrides Function CloneCore() As ClientCredentials ' Implement the cloning functionality. Return New MyClientCredentials(Me) End Function End Class
To implement a custom client security token manager
Define a new class derived from ClientCredentialsSecurityTokenManager.
Optional. Override the CreateSecurityTokenProvider(SecurityTokenRequirement) method if a custom SecurityTokenProvider implementation must be created. For more information about custom security token providers, see How to: Create a Custom Security Token Provider.
Optional. Override the CreateSecurityTokenAuthenticator(SecurityTokenRequirement, SecurityTokenResolver) method if a custom SecurityTokenAuthenticator implementation must be created. For more information about custom security token authenticators, see How to: Create a Custom Security Token Authenticator.
Optional. Override the CreateSecurityTokenSerializer method if a custom SecurityTokenSerializer must be created. For more information about custom security tokens and custom security token serializers, see How to: Create a Custom Token.
internal class MyClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager { MyClientCredentials credentials; public MyClientCredentialsSecurityTokenManager(MyClientCredentials credentials) : base(credentials) { this.credentials = credentials; } public override SecurityTokenProvider CreateSecurityTokenProvider( SecurityTokenRequirement tokenRequirement) { // Return your implementation of the SecurityTokenProvider, if required. // This implementation delegates to the base class. return base.CreateSecurityTokenProvider(tokenRequirement); } public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator( SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver) { // Return your implementation of the SecurityTokenAuthenticator, if required. // This implementation delegates to the base class. return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver); } public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version) { // Return your implementation of the SecurityTokenSerializer, if required. // This implementation delegates to the base class. return base.CreateSecurityTokenSerializer(version); } }
Friend Class MyClientCredentialsSecurityTokenManager Inherits ClientCredentialsSecurityTokenManager Private credentials As MyClientCredentials Public Sub New(ByVal credentials As MyClientCredentials) MyBase.New(credentials) Me.credentials = credentials End Sub Public Overrides Function CreateSecurityTokenProvider( _ ByVal tokenRequirement As SecurityTokenRequirement) As SecurityTokenProvider ' Return your implementation of the SecurityTokenProvider, if required. ' This implementation delegates to the base class. Return MyBase.CreateSecurityTokenProvider(tokenRequirement) End Function Public Overrides Function CreateSecurityTokenAuthenticator( _ ByVal tokenRequirement As SecurityTokenRequirement, _ ByRef outOfBandTokenResolver As SecurityTokenResolver) As SecurityTokenAuthenticator ' Return your implementation of the SecurityTokenAuthenticator, if required. ' This implementation delegates to the base class. Return MyBase.CreateSecurityTokenAuthenticator(tokenRequirement, outOfBandTokenResolver) End Function Public Overrides Function CreateSecurityTokenSerializer(ByVal version As SecurityTokenVersion) _ As SecurityTokenSerializer ' Return your implementation of the SecurityTokenSerializer, if required. ' This implementation delegates to the base class. Return MyBase.CreateSecurityTokenSerializer(version) End Function End Class
To use a custom client credentials from application code
Either create an instance of the generated client that represents the service interface, or create an instance of the ChannelFactory pointing to a service you want to communicate with.
Remove the system-provided client credentials behavior from the Behaviors collection, which can be accessed through the Endpoint property.
Create a new instance of a custom client credentials class and add it to the Behaviors collection, which can be accessed through the Endpoint property.
// Create a client with the client endpoint configuration. CalculatorClient client = new CalculatorClient(); // Remove the ClientCredentials behavior. client.ChannelFactory.Endpoint.Behaviors.Remove<ClientCredentials>(); // Add a custom client credentials instance to the behaviors collection. client.ChannelFactory.Endpoint.Behaviors.Add(new MyClientCredentials());
' Create a client with the client endpoint configuration. Dim client As New CalculatorClient() ' Remove the ClientCredentials behavior. client.ChannelFactory.Endpoint.Behaviors.Remove(Of ClientCredentials)() ' Add a custom client credentials instance to the behaviors collection. client.ChannelFactory.Endpoint.Behaviors.Add(New MyClientCredentials())
The previous procedure shows how to use client credentials from application code. WCF credentials can also be configured using the application configuration file. Using application configuration is often preferable to hard-coding because it enables modification of application parameters without having to modify the source, recompiling, and redeployment.
The next procedure describes how to provide support for configuration of custom credentials.
Creating a configuration handler for custom client credentials
Define a new class derived from ClientCredentialsElement.
Optional. Add properties for all additional configuration parameters that you want to expose through application configuration. The example below adds one property named
CreditCardNumber
.Override the BehaviorType property to return the type of the custom client credentials class created with the configuration element.
Override the CreateBehavior method. The method is responsible for creating and returning an instance of the custom credential class based on the settings loaded from the configuration file. Call the base ApplyConfiguration(ClientCredentials) method from this method to retrieve the system-provided credentials settings loaded into your custom client credentials instance.
Optional. If you added additional properties in step 2, you need to override the Properties property in order to register your additional configuration settings for the configuration framework to recognize them. Combine your properties with the base class properties to allow the system-provided settings to be configured through this custom client credentials configuration element.
public class MyClientCredentialsConfigHandler : ClientCredentialsElement { ConfigurationPropertyCollection properties; public override Type BehaviorType { get { return typeof(MyClientCredentials); } } public string CreditCardNumber { get { return (string)base["creditCardNumber"]; } set { if (String.IsNullOrEmpty(value)) { value = String.Empty; } base["creditCardNumber"] = value; } } protected override ConfigurationPropertyCollection Properties { get { if (this.properties == null) { ConfigurationPropertyCollection properties = base.Properties; properties.Add(new ConfigurationProperty( "creditCardNumber", typeof(System.String), string.Empty, null, new StringValidator(0, 32, null), ConfigurationPropertyOptions.None)); this.properties = properties; } return this.properties; } } protected override object CreateBehavior() { MyClientCredentials creds = new MyClientCredentials(); creds.CreditCardNumber = CreditCardNumber; base.ApplyConfiguration(creds); return creds; } }
Public Class MyClientCredentialsConfigHandler Inherits ClientCredentialsElement Private propertiesValue As ConfigurationPropertyCollection Public Overrides ReadOnly Property BehaviorType() As Type Get Return GetType(MyClientCredentials) End Get End Property Public Property CreditCardNumber() As String Get Return CStr(MyBase.Item("creditCardNumber")) End Get Set If String.IsNullOrEmpty(value) Then value = String.Empty End If MyBase.Item("creditCardNumber") = value End Set End Property Protected Overrides ReadOnly Property Properties() As ConfigurationPropertyCollection Get If Me.propertiesValue Is Nothing Then Dim myProperties As ConfigurationPropertyCollection = MyBase.Properties myProperties.Add(New ConfigurationProperty( _ "creditCardNumber", _ GetType(System.String), _ String.Empty, _ Nothing, _ New StringValidator(0, 32, Nothing), _ ConfigurationPropertyOptions.None)) Me.propertiesValue = myProperties End If Return Me.propertiesValue End Get End Property Protected Overrides Function CreateBehavior() As Object Dim creds As New MyClientCredentials() creds.CreditCardNumber = Me.CreditCardNumber MyBase.ApplyConfiguration(creds) Return creds End Function End Class
Once you have the configuration handler class, it can be integrated into the WCF configuration framework. That enables the custom client credentials to be used in the client endpoint behavior elements, as shown in the next procedure.
To register and use a custom client credentials configuration handler in the application configuration
Add an
<extensions>
element and a<behaviorExtensions>
element to the configuration file.Add an
<add>
element to the<behaviorExtensions>
element and set thename
attribute to an appropriate value.Set the
type
attribute to the fully-qualified type name. Also include the assembly name and other assembly attributes.<system.serviceModel> <extensions> <behaviorExtensions> <add name="myClientCredentials" type="Microsoft.ServiceModel.Samples.MyClientCredentialsConfigHandler, CustomCredentials, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> </system.serviceModel>
After registering your configuration handler, the custom credentials element can be used inside the same configuration file instead of the system-provided
<clientCredentials>
element. You can use both the system-provided properties and any new properties that you have added to your configuration handler implementation. The following example sets the value of a custom property using thecreditCardNumber
attribute.<behaviors> <endpointBehaviors> <behavior name="myClientCredentialsBehavior"> <myClientCredentials creditCardNumber="123-123-123"/> </behavior> </endpointBehaviors> </behaviors>
To implement custom service credentials
Define a new class derived from ServiceCredentials.
Optional. Add new properties to provide APIs for new credential values that are being added. If you do not add new credential values, skip this step. The following example adds an
AdditionalCertificate
property.Override the CreateSecurityTokenManager method. This method is automatically called by the WCF infrastructure when the custom client credential is used. The method is responsible for creating and returning an instance of an implementation of the SecurityTokenManager class (described in the next procedure).
Optional. Override the CloneCore method. This is required only if adding new properties or internal fields to the custom client credentials implementation.
public class MyServiceCredentials : ServiceCredentials { X509Certificate2 additionalCertificate; public MyServiceCredentials() { } protected MyServiceCredentials(MyServiceCredentials other) : base(other) { this.additionalCertificate = other.additionalCertificate; } public X509Certificate2 AdditionalCertificate { get { return this.additionalCertificate; } set { if (value == null) { throw new ArgumentNullException("value"); } this.additionalCertificate = value; } } public override SecurityTokenManager CreateSecurityTokenManager() { return base.CreateSecurityTokenManager(); } protected override ServiceCredentials CloneCore() { return new MyServiceCredentials(this); } }
Public Class MyServiceCredentials Inherits ServiceCredentials Private additionalCertificateValue As X509Certificate2 Public Sub New() End Sub Protected Sub New(ByVal other As MyServiceCredentials) MyBase.New(other) Me.additionalCertificate = other.additionalCertificate End Sub Public Property AdditionalCertificate() As X509Certificate2 Get Return Me.additionalCertificateValue End Get Set If value Is Nothing Then Throw New ArgumentNullException("value") End If Me.additionalCertificateValue = value End Set End Property Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager Return MyBase.CreateSecurityTokenManager() End Function Protected Overrides Function CloneCore() As ServiceCredentials Return New MyServiceCredentials(Me) End Function End Class
To implement a custom service security token manager
Define a new class derived from the ServiceCredentialsSecurityTokenManager class.
Optional. Override the CreateSecurityTokenProvider method if a custom SecurityTokenProvider implementation must be created. For more information about custom security token providers, see How to: Create a Custom Security Token Provider.
Optional. Override the CreateSecurityTokenAuthenticator method if a custom SecurityTokenAuthenticator implementation must be created. For more information about custom security token authenticators, see How to: Create a Custom Security Token Authenticator topic.
Optional. Override the CreateSecurityTokenSerializer(SecurityTokenVersion) method if a custom SecurityTokenSerializer must be created. For more information about custom security tokens and custom security token serializers, see How to: Create a Custom Token.
internal class MyServiceCredentialsSecurityTokenManager : ServiceCredentialsSecurityTokenManager { MyServiceCredentials credentials; public MyServiceCredentialsSecurityTokenManager(MyServiceCredentials credentials) : base(credentials) { this.credentials = credentials; } public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement) { // Return your implementation of SecurityTokenProvider, if required. // This implementation delegates to the base class. return base.CreateSecurityTokenProvider(tokenRequirement); } public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver) { // Return your implementation of SecurityTokenProvider, if required. // This implementation delegates to the base class. return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver); } public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version) { // Return your implementation of SecurityTokenProvider, if required. // This implementation delegates to the base class. return base.CreateSecurityTokenSerializer(version); } }
Friend Class MyServiceCredentialsSecurityTokenManager Inherits ServiceCredentialsSecurityTokenManager Private credentials As MyServiceCredentials Public Sub New(ByVal credentials As MyServiceCredentials) MyBase.New(credentials) Me.credentials = credentials End Sub Public Overrides Function CreateSecurityTokenProvider(ByVal tokenRequirement As SecurityTokenRequirement) _ As SecurityTokenProvider ' Return your implementation of SecurityTokenProvider, if required. ' This implementation delegates to the base class. Return MyBase.CreateSecurityTokenProvider(tokenRequirement) End Function Public Overrides Function CreateSecurityTokenAuthenticator( _ ByVal tokenRequirement As SecurityTokenRequirement, _ ByRef outOfBandTokenResolver As SecurityTokenResolver) _ As SecurityTokenAuthenticator ' Return your implementation of SecurityTokenProvider, if required. ' This implementation delegates to the base class. Return MyBase.CreateSecurityTokenAuthenticator(tokenRequirement, outOfBandTokenResolver) End Function Public Overrides Function CreateSecurityTokenSerializer(ByVal version As SecurityTokenVersion) _ As SecurityTokenSerializer ' Return your implementation of SecurityTokenProvider, if required. ' This implementation delegates to the base class. Return MyBase.CreateSecurityTokenSerializer(version) End Function End Class
To use custom service credentials from application code
Create an instance of the ServiceHost.
Remove the system-provided service credentials behavior from the Behaviors collection.
Create a new instance of the custom service credentials class and add it to the Behaviors collection.
// Create a service host with a service type. ServiceHost serviceHost = new ServiceHost(typeof(Service)); // Remove the default ServiceCredentials behavior. serviceHost.Description.Behaviors.Remove<ServiceCredentials>(); // Add a custom service credentials instance to the collection. serviceHost.Description.Behaviors.Add(new MyServiceCredentials());
' Create a service host with a service type. Dim serviceHost As New ServiceHost(GetType(Service)) ' Remove the default ServiceCredentials behavior. serviceHost.Description.Behaviors.Remove(Of ServiceCredentials)() ' Add a custom service credentials instance to the collection. serviceHost.Description.Behaviors.Add(New MyServiceCredentials())
Add support for configuration using the steps described previously in the procedures "To create a configuration handler for custom client credentials
" and "To register and use a custom client credentials configuration handler in the application configuration
." The only difference is to use the ServiceCredentialsElement class instead of the ClientCredentialsElement class as a base class for the configuration handler. The custom service credential element can then be used wherever the system-provided <serviceCredentials>
element is used.