How to: Authenticate with a Client Certificate to a WCF Service Protected by ACS
Updated: January 4, 2013
Applies To
-
Microsoft® Windows® Communication Foundation (WCF)
-
Windows Azure Active Directory Access Control (also known as Access Control Service or ACS)
Summary
This topic shows how to write code for WCF clients that communicate with WCF services that require federated authentication using Windows Azure Active Directory Access Control (also known as Access Control Service or ACS). Specifically, it explains how to request a token from ACS based on a client certificate.
Contents
-
Objectives
-
Overview
-
What You Should Know
-
Summary of Steps
-
Step 1 – Configure a Relying Party Using the ACS Management Portal
-
Step 2 – Create a Service Identity and Configuring It to Respond to Token Requests Based on a Client Certificate
-
Step 3 – Implement a WS-Trust Binding for an Issued Token Based on a Client Certificate
-
Step 4 – Create a WCF Service
-
Step 5 – Create a WCF Client
-
Step 6 – Test Your Solution
-
Considerations
-
Related Items
Objectives
-
Identify and collect necessary configuration parameters.
-
Implement a WS-Trust issued token binding based on a client certificate.
Overview
The WCF service can use ACS for authentication. This approach is called federated authentication. Using this approach, credentials (in this a case client certificate) are managed by ACS. A WCF client submits credentials to ACS and receives a valid token upon successful authentication. This token is presented to the WCF service for validation. If the validation is successful, the request is processed by the service. The token request is implemented based on the WS-Trust protocol. CertificateWSTrustBinding and IssuedTokenWSTrustBinding are key classes that help in orchestrating credential and token exchanges between a WCF client, ACS, and the WCF service.
What You Should Know
In this How-To topic, you will use several X.509 certificates of different types and for different purposes. This is the list of certificates you should have beforehand to make sure the code works properly. Review Obtain an X.509 Certificate section in How to: Configure Trust Between ACS and ASP.NET Web Applications Using X.509 Certificates topic for the options how to obtain X.509 certificate. Note that the certificate file names below are arbitrary and yours may vary.
-
ACS2ClientCertificate.cer – This is a X.509 certificate with public key only. It is used by ACS when configuring service identity credential.
-
ACS2ClientCertificate.pfx – This is a X.509 certificate with both private and public keys. It is used by the WCF client when authenticating to ACS. It is configured in the WCF’s client app.config file.
-
ACS2SigningCertificate.cer – This is a X.509 certificate with public key only. It is used by the WCF service to validate tokens signed by ACS. It is configured in the WCF service app.config file.
-
ACS2SigningCertificate.pfx – This is a X.509 certificate with both private and public keys. It is used by ACS to sign tokens it issues. It is configured in the ACS management portal in the Token Signing Certificates section on the Relying Party Application Settings page.
-
WcfServiceCertificate.cer – This is a X.509 certificate with a public key only. It is used by ACS to encrypt tokens issued to WCF service. It is configured in the ACS management portal in the Token Encryption Certificates section on the Relying Party Application Settings page.
-
WcfServiceCertificate.pfx – This is a X.509 certificate with both private and public keys. This is used by the WCF service to decrypt tokens issued by ACS. It is configured in the WCF service app.config file.
For the purpose of the demonstration, you can use the certificates that are available with the code samples download package. You cannot use those certificates for the production environments.
Summary of Steps
-
Step 1 – Configure a Relying Party Using the ACS Management Portal
-
Step 2 – Create a Service Identity and Configuring It to Respond to Token Requests Based on a Client Certificate
-
Step 3 – Implement a WS-Trust Binding for an Issued Token Based on a Client Certificate
-
Step 4 – Create a WCF Service
-
Step 5 – Create a WCF Client
-
Step 6 – Test Your Solution
Step 1 – Configure a Relying Party Using the ACS Management Portal
This step shows how to configure a WCF service as a relying party using the ACS management portal.
To configure WCF Service as a relying party
-
Go to the Windows Azure Management Portal, sign in, and then click Active Directory.
-
To manage an Access Control namespace, select the namespace, and then click Manage. (Or, click Access Control Namespaces, select the namespace, and then click Manage.)
-
In the Trust Relationships section, click Relying Party Applications.
-
On the Relying Party Applications page, click the Add link. The Add Relying Party Application page opens.
-
In the Relying Party Application Settings section, make the following selections:
-
Name—Specify the display name for this relying party. For example, Certificate Binding Sample RP.
-
Mode—Select the Enter settings manually option.
-
Realm—Specify the realm of your WCF service. For example, http://localhost:7000/Service/Default.aspx.
-
Return URL—Leave blank.
-
Error URL—Leave blank.
-
Token format—Select the SAML 2.0 option.
-
Token encryption policy—Select the Require encryption option.
-
Token lifetime (secs)—Leave the default, 600 seconds.
-
Name—Specify the display name for this relying party. For example, Certificate Binding Sample RP.
-
In the Authentication Settings section, make the following selections:
-
Identity providers—Leave all unchecked.
-
Rule groups—Select the Create New Rule Group option.
-
Identity providers—Leave all unchecked.
-
In the Token Signing Settings section, select Use service namespace certificate (standard). Or, if you use your own signing certificate make the following selections:
-
Token signing—Select the Use a dedicated certificate option.
-
Certificate:
- File—Browse for an X.509 certificate with a private key (.pfx file) to use for when signing.
- Password—Enter the password for the .pfx file in the field above.
- File—Browse for an X.509 certificate with a private key (.pfx file) to use for when signing.
-
Token signing—Select the Use a dedicated certificate option.
-
This is required if the WCF service requires encryption. Use the .cer file with only public key that comes from the certificate with the private key that WCF uses for decrypting messages. In the Token Encryption section, make the following selection:
-
Certificate:
- File—Browse to load the X.509 certificate (.cer file) for token encryption for this relying party application.
- File—Browse to load the X.509 certificate (.cer file) for token encryption for this relying party application.
-
Certificate:
-
Click Save.
Saving your work also triggered creating rule group . Now you need to generate rules for the rule group.
To generate rules in the rule group
-
In the ACS portal, click Rule groups.
-
On the Rule Groups page, click the Default Rule Group for Certificate Binding Sample RP that you created earlier in this topic.
-
On the Edit Rule Group page, click Add .
-
On the Add Claim Rule page, in the Input claim type section, select Access Control Service. To create a rule that passes through all claims, leave the default values for the remaining options.
-
Click Save.
Step 2 – Create a Service Identity and Configuring It to Respond to Token Requests Based on a Client Certificate
This step shows how to create and configure a service identity to respond to a token request based on a client certificate.
To configure a service identity to use client certificate
-
Go to the Windows Azure Management Portal, sign in, and then click Active Directory.
-
To manage an Access Control namespace, select the namespace, and then click Manage. (Or, click Access Control Namespaces, select the namespace, and then click Manage.)
-
Click Service identities and then click Add.
-
On the Add Service Identities page, enter the following information in the specified fields.
-
Name: Provide any name.
-
Description: Add a description. This content is optional.
-
Type: Select X.509 Certificate.
-
Certificate— Click Browse, and navigate to the X.509 certificate (.CER file) for the credential.
-
Name: Provide any name.
Step 3 – Implement a WS-Trust Binding for an Issued Token Based on a Client Certificate
This step shows how to create the WS-Trust binding library required to communicate with ACS to request a token based on a client certificate. This library will be used with both the WCF client and the WCF service.
To create a WS-Trust binding
-
In Visual Studio® 2010 create a new empty solution, add a class library project, and then give it a name, for example, .
-
Add a reference to the following three assemblies:
-
System.ServiceModel
-
System.IdentityModel
-
Microsoft.IdentityModel
-
System.ServiceModel
-
Add a public static class, and then give it a name, for example, Bindings.
-
Add the following declarations.
using System.ServiceModel; using System.ServiceModel.Channels; using Microsoft.IdentityModel.Protocols.WSTrust.Bindings;
-
Add a method that creates a certificate WS-Trust binding.
public static Binding CreateAcsCertificateBinding() { return new CertificateWSTrustBinding(SecurityMode.TransportWithMessageCredential); } -
Add a method that creates a username-issued token WS-Trust binding.
public static Binding CreateServiceBinding(string acsCertificateEndpoint) { return new IssuedTokenWSTrustBinding(CreateAcsCertificateBinding(), new EndpointAddress(acsCertificateEndpoint)); } -
Save your work. The completed code for your WS-Trust binding should look similar to the following.
public static class Bindings { public static Binding CreateServiceBinding(string acsCertificateEndpoint) { return new IssuedTokenWSTrustBinding(CreateAcsCertificateBinding(), new EndpointAddress(acsCertificateEndpoint)); } public static Binding CreateAcsCertificateBinding() { return new CertificateWSTrustBinding(SecurityMode.TransportWithMessageCredential); } } -
Compile the project to make sure there are no compilation errors.
Step 4 – Create a WCF Service
This step shows how to create a WCF service that requires a token issued by ACS. As an example, you will create simple WCF service that accepts strings and reverses it.
To create a WCF service that requires a token issued by ACS
-
In Visual Studio 2010 create a new Windows Console Application project. For simplicity, the WCF service will be self-hosted in the console application.
-
Add a reference to the WSTrustBinding project you created in previous step.
-
Add a reference to the following three assemblies:
-
System.ServiceModel
-
System.IdentityModel
-
Microsoft.IdentityModel
-
System.Configuration
-
System.ServiceModel
-
Create a service contract. To create a service contract, add a new class library project and then give it a name, for example, Interfaces.
-
Add an interface file to the Interfaces project with a public interface. Give it a name, for example, IStringService.
-
Add a reference to the System.ServiceModel assembly.
-
Add the following declaration to the top.
using System.ServiceModel;
-
Add code similar to the following.
[ServiceContract] public interface IStringService { [OperationContract] string Reverse( string value ); } -
Switch back to the WCF service console application and add a reference to the Interfaces project you have just created.
-
Add a services implementation based on the service contract. To add a service implementation based on the service contract, add a new class file to the project. Give it a name, for example, StringService.cs.
-
Add an implementation similar to the following.
public class StringService : IStringService { public string Reverse( string value ) { char[] chars = value.ToCharArray(); Array.Reverse( chars ); return new string( chars ); } } -
Implement IssuerNameRegistry. IssuerNameRegistry is a trust decision point to reject unknown or untrusted token issuers. It is a security measure. To implement IssuerNameRegistry, add a new class file to the WCF service project and give it a name, for example, X509IssuerNameRegistry.cs.
-
Add the following declarations at the top.
using System.Collections.Generic; using System.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens; using Interfaces;
-
Add an implementation similar to the following.
/// <summary> /// Implements an IssuerNameRegistry that only recognizes a specific /// set of issuer subject names. /// </summary> class X509IssuerNameRegistry : IssuerNameRegistry { List<string> _trustedSubjectNames = new List<string>(); /// <summary> /// Constructs an instance of X509IssuerNameRegistry. /// </summary> /// <param name="trustedSubjectNames">The subject names that can be recognized.</param> public X509IssuerNameRegistry( params string[] trustedSubjectNames ) { _trustedSubjectNames = new List<string>( trustedSubjectNames ); } /// <summary> /// Determines what the issuer name will be on claims contained in tokens. /// </summary> /// <param name="securityToken"> /// The security token to extract the issuer name from. This token typically signed the /// token containing claims and represents the issuer. /// </param> /// <returns>The issuer name to be put on claims.</returns> public override string GetIssuerName( SecurityToken securityToken ) { X509SecurityToken x509Token = securityToken as X509SecurityToken; if ( x509Token != null ) { // // Check the list of trusted/permissible issuers // if ( _trustedSubjectNames.Contains( x509Token.Certificate.SubjectName.Name ) ) { return x509Token.Certificate.SubjectName.Name; } } // // Complain in all other situations. // throw new SecurityTokenException( "Untrusted issuer." ); } } -
Implement the service host instantiation.
-
Add the following declarations to the top of the Program class.
using System.Configuration; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.ServiceModel.Security; using Microsoft.IdentityModel.Configuration; using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens.Saml2; using WSTrustBinding; using Interfaces;
-
To implement the service host instantiation, add the following code to the Program class (above the Main method).
static string AccessControlHostName = ConfigurationManager.AppSettings.Get( "AccessControlHostName" ); static string AccessControlNamespace = ConfigurationManager.AppSettings.Get( "AccessControlNamespace" ); static string AccessControlSigningCertificateFilePath = ConfigurationManager.AppSettings.Get( "AccessControlSigningCertificateFilePath" ); static string ServiceAddress = ConfigurationManager.AppSettings.Get( "ServiceAddress" ); static string ServiceCertificateFilePath = ConfigurationManager.AppSettings.Get( "ServiceCertificateFilePath" ); static string ServiceCertificatePassword = ConfigurationManager.AppSettings.Get( "ServiceCertificatePassword" ); private static X509Certificate2 GetAcsSigningCertificate() { return new X509Certificate2( AccessControlSigningCertificateFilePath ); } private static X509Certificate2 GetServiceCertificateWithPrivateKey() { return new X509Certificate2( ServiceCertificateFilePath, ServiceCertificatePassword ); } private static ServiceHost CreateWcfServiceHost() { string acsUsernameEndpoint = String.Format( "https://{0}.{1}/v2/wstrust/13/username", AccessControlNamespace, AccessControlHostName ); ServiceHost rpHost = new ServiceHost( typeof( StringService ) ); rpHost.Credentials.ServiceCertificate.Certificate = GetServiceCertificateWithPrivateKey(); rpHost.AddServiceEndpoint( typeof( IStringService ), Bindings.CreateServiceBinding( acsUsernameEndpoint ), ServiceAddress ); // // This must be called after all WCF settings are set on the service host so the // Windows Identity Foundation token handlers can pick up the relevant settings. // ServiceConfiguration serviceConfiguration = new ServiceConfiguration(); serviceConfiguration.CertificateValidationMode = X509CertificateValidationMode.None; // Accept ACS signing certificate as Issuer. serviceConfiguration.IssuerNameRegistry = new X509IssuerNameRegistry( GetAcsSigningCertificate().SubjectName.Name ); // Add the SAML 2.0 token handler. serviceConfiguration.SecurityTokenHandlers.AddOrReplace( new Saml2SecurityTokenHandler() ); // Add the address of this service to the allowed audiences. serviceConfiguration.SecurityTokenHandlers.Configuration.AudienceRestriction.AllowedAudienceUris.Add( new Uri( ServiceAddress ) ); FederatedServiceCredentials.ConfigureServiceHost( rpHost, serviceConfiguration ); return rpHost; } -
Specify the following configuration information in the app.config file.
<appSettings> <!-- ACS v2 configuration --> <add key="AccessControlHostName" value="accesscontrol.windows.net"/> <add key="AccessControlNamespace" value="...enter your Access Control namespace name..."/> <add key="AccessControlSigningCertificateFilePath" value="...update to path to ACS signing certificate (.cer)..." /> <!-- Service configuration --> <add key="ServiceAddress" value="...update to your service address..." /> <add key="ServiceCertificateFilePath" value="...update to your service certificate path..."/> <add key="ServiceCertificatePassword" value="...update to your service certificate password..."/> </appSettings> -
Instantiate the service host. To instantiate the service host, add the following code to the Main method of the Program class.
ServiceHost rpHost = CreateWcfServiceHost(); rpHost.Open(); Console.WriteLine( "StringService has been started." ); Console.WriteLine(); Console.WriteLine( "Press <ENTER> to exit" ); Console.WriteLine(); Console.ReadLine(); rpHost.Close(); -
Set the WCF service project as a startup project by right-clicking it in the Solution Explorer, and then selecting the Set as StartUp Project option.
-
Update app.config with your ACS information.
-
Test your work by pressing F5 and running the project.
-
You should see the following message.
StringService has been started. Press <ENTER> to exit
-
If you see error message similar to the following, you can resolve it either by following the link and following the instructions or by running Visual Studio 2010 in elevated mode as an Administrator.
HTTP could not register URL http://+:7000/Service/Default.aspx/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).
Step 5 – Create a WCF Client
This step shows how to establish a channel from a client to a WCF service that requires ACS federated authentication.
To create a WCF client
-
In Visual Studio 2010, in the existing solution create a new Windows Console Application project. Give it a name, for example WCFClient.
-
Add a reference to the WSTrustBinding you project created earlier.
-
Add a reference to the Interfaces project.
-
Add a reference to the following three assemblies:
-
System.ServiceModel
-
System.IdentityModel
-
Microsoft.IdentityModel
-
System.Configuration
-
System.ServiceModel
-
Add the following declarations to the top of the Program class.
using System.Configuration; using System.Diagnostics; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Security; using Interfaces; using WSTrustBinding;
-
Implement ChannelFactory for your service. Add code similar to the following to the Program class.
static string AccessControlHostName = ConfigurationManager.AppSettings.Get( "AccessControlHostName" ); static string AccessControlNamespace = ConfigurationManager.AppSettings.Get( "AccessControlNamespace" ); static string ServiceAddress = ConfigurationManager.AppSettings.Get( "ServiceAddress" ); static string ServiceCertificateFilePath = ConfigurationManager.AppSettings.Get( "ServiceCertificateFilePath" ); static string ClientCertificateFilePath = ConfigurationManager.AppSettings.Get("ClientCertificateFilePath"); static string ClientCertificatePassword = ConfigurationManager.AppSettings.Get("ClientCertificatePassword"); private static ChannelFactory<IStringService> CreateChannelFactory(string acsEndpoint, string serviceEndpoint) { // // The WCF service endpoint host name may not match the service certificate subject. // By default, the host name is 'localhost' and the certificate subject is 'WcfServiceCertificate'. // Create a DNS Endpoint identity to match WcfServiceCertificate. // EndpointAddress serviceEndpointAddress = new EndpointAddress( new Uri( serviceEndpoint ), EndpointIdentity.CreateDnsIdentity( GetServiceCertificateSubjectName() ), new AddressHeaderCollection() ); ChannelFactory<IStringService> stringServiceFactory = new ChannelFactory<IStringService>(Bindings.CreateServiceBinding(acsEndpoint), serviceEndpointAddress); // Set the service credentials and disable certificate validation to work with sample certificates stringServiceFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; stringServiceFactory.Credentials.ServiceCertificate.DefaultCertificate = GetServiceCertificate(); // Set the client credentials. stringServiceFactory.Credentials.ClientCertificate.Certificate = GetClientCertificateWithPrivateKey(); return stringServiceFactory; } private static X509Certificate2 GetClientCertificateWithPrivateKey() { return new X509Certificate2(ClientCertificateFilePath, ClientCertificatePassword); } private static X509Certificate2 GetServiceCertificate() { return new X509Certificate2( ServiceCertificateFilePath ); } private static string GetServiceCertificateSubjectName() { const string cnPrefix = "CN="; string subjectFullName = GetServiceCertificate().Subject; Debug.Assert( subjectFullName.StartsWith( cnPrefix ) ); return subjectFullName.Substring( cnPrefix.Length ); } -
Add the following code to the Main method; this results in the actual invocation of the service.
Console.WriteLine( "Enter a string to reverse, then press <ENTER>" ); string userInputString = Console.ReadLine(); Console.WriteLine(); string acsUsernameEndpoint = String.Format( "https://{0}.{1}/v2/wstrust/13/username", AccessControlNamespace, AccessControlHostName ); ChannelFactory<IStringService> stringServiceFactory = CreateChannelFactory (acsUsernameEndpoint, ServiceAddress ); IStringService stringService = stringServiceFactory.CreateChannel(); ICommunicationObject channel = (ICommunicationObject)stringService; string outputString = stringService.Reverse( userInputString ); Console.WriteLine( "Service responded with: " + outputString ); Console.WriteLine(); Console.WriteLine( "Press <ENTER> to exit" ); Console.ReadLine(); channel.Close(); -
Add the following configuration information to the app.config file. Note: You must update the settings of the configuration below with your values to make sure the code works properly.
<appSettings> <!-- ACS v2 configuration --> <add key="AccessControlHostName" value="accesscontrol.windows.net"/> <add key="AccessControlNamespace" value="...enter your Access Control namespace name..."/> <!-- Service configuration --> <add key="ServiceAddress" value=" " /> <add key="ServiceCertificateFilePath" value="..path to service certificate file (.CER).. "/> <!-- Client configuration --> <add key="ClientCertificateFilePath" value="..path to client certificate (.PFX).."/> <add key="ClientCertificatePassword" value="..pfx file password.."/> </appSettings> -
Update the app.config with your ACS data.
Step 6 – Test Your Solution
This step shows how to test your solution.
To test your solution
-
Run the WCF service first by pressing F5; it should be configured as a startup project. You should see new console application start with the following message displayed.
StringService has been started. Press <ENTER> to exit
-
Run the WCF client by right-clicking the WCF client project in Solution Explorer, then select the Debug option, and then select the Start new instance option. You should see another console application start with the following message displayed.
Enter a string to reverse, then press <ENTER>
-
Enter an arbitrary string, for example, hello world!, and then press Enter.
-
You should see the following response on the console application.
hello world! Service responded with: !dlrow olleh Press <ENTER> to exit
Considerations
-
Security—In the accompanying sample there is a TrustAllCertificates() function. It is for the demonstration purposes only. It should be removed and valid certificates should be used for SSL. Also, in the accompanying sample, the CertificateValidationMode is set to X509CertificateValidationMode.None. This is for the demonstration purposes only. It should be set to PeerOrChainTrust.
Related Items