Expand Minimize
2 out of 6 rated this helpful - Rate this topic

How to: Authenticate with a Username and Password to a WCF Service Protected by ACS

Published: April 7, 2011

Updated: January 4, 2013

Applies To: Windows Azure

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 use Windows Azure Active Directory Access Control (also known as Access Control Service or ACS) to write code for WCF clients that communicate with WCF services that require federated authentication. Specifically, it outlines the steps required to request a token from ACS based on a username and password pair.

Contents

  • Objectives

  • Overview

  • 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 Username and Password

  • Step 3 – Implement a WS-Trust Binding for an Issued Token Based on a Username and Password

  • 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 username and password.

Overview

The WCF service can utilize ACS for authentication. This approach is called federated authentication. Using this approach, credentials (in this a case username and password pair) 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. UserNameWSTrustBinding and IssuedTokenWSTrustBinding are key classes that help in orchestrating credential and token exchanges between a WCF client, ACS, and the WCF service.

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 Username and Password

  • Step 3 – Implement a WS-Trust Binding for an Issued Token Based on a Username and Password

  • 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

  1. Go to the Windows Azure Management Portal, sign in, and then click Active Directory.

  2. 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.)

  3. Click Relying party applications and then click Add.

  4. On the Add Relying Party Application page, make the following selections and then click Save.

    • Name—Specify the display name for this relying party. For example, Username 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.

  5. In the Authentication Settings section, make the following selections:

    • Identity providers—Leave all unchecked.

    • Rule groups—Select the Create New Rule Group option.

  6. In the Token Signing Settings section, 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.

  7. 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.

  8. 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

  1. Click Rule groups.

  2. Click Default Rule Group for Username Binding Sample RP (you created this group earlier).

  3. On the Edit Rule Group page, click Add .

  4. On the Add Claim Rule page, in the If section, select the Access Control Service option. Leave the default values for the rest.

  5. Click Save.

Step 2 – Create a Service Identity and Configuring It to Respond to Token Requests Based on a Username and Password

This step shows how to create and configure a service identity to respond to a token request based on a username and password.

To configure a service identity to use username/password credentials

  1. Go to the Windows Azure Management Portal, sign in, and then click Active Directory.

  2. 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.)

  3. Click Service identities and then click Add.

  4. On theService Identity page provide a name in the Name field. You will use it as the username for your username/password pair when requesting a token.

  5. Optionally, add a description in the Description section.

  6. In the Credential Settings page, provide the following information:

    • Display name—The display name for this set of credentials.

    • Type—Select Password from the drop-down list.

    • Password—Enter your desired password. You will use it as the password for your username/password pair when requesting a token.

    • Effective date—Enter the effective date for this credential.

    • Expiration date—Enter the expiration date for this credential.

  7. On the Edit Service Identity page, click Save, and then click Save again.

Step 3 – Implement a WS-Trust Binding for an Issued Token Based on a Username and Password

This step shows how to create the WS-Trust binding library required to communicate with ACS to request a token based on a username/password pair. This library will be used with both the WCF client and the WCF service.

To create a WS-Trust binding

  1. In Visual Studio® 2010 create a new empty solution, add a class library project, and then give it a name, for example, WSTrustBinding.

  2. Add a reference to the following three assemblies:

    • System.ServiceModel

    • System.IdentityModel

    • Microsoft.IdentityModel

  3. Add a public static class, and then give it a name, for example, Bindings.

  4. Add the following declarations.

    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using Microsoft.IdentityModel.Protocols.WSTrust.Bindings;
    
    
    
  5. Add a method that creates a username WS-Trust binding.

    public static Binding CreateAcsUsernameBinding()
    {
        return new UserNameWSTrustBinding( SecurityMode.TransportWithMessageCredential );            
    }
    
    
  6. Add a method that creates a username-issued token WS-Trust binding.

    public static Binding CreateServiceBinding( string acsUsernameEndpoint )
    {
        return new IssuedTokenWSTrustBinding( CreateAcsUsernameBinding(), new EndpointAddress( acsUsernameEndpoint ) );                                                                                          
    }
    
    
  7. 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 acsUsernameEndpoint )
        {
            return new IssuedTokenWSTrustBinding( CreateAcsUsernameBinding(), new EndpointAddress( acsUsernameEndpoint ) );                                                                                          
        }
    
        public static Binding CreateAcsUsernameBinding()
        {
            return new UserNameWSTrustBinding( SecurityMode.TransportWithMessageCredential );            
        }
    }
    
    
  8. 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

  1. In Visual Studio 2010 create a new Windows Console Application project. For simplicity, the WCF service will be self-hosted in the console application.

  2. Add a reference to the WSTrustBinding project you created in previous step.

  3. Add a reference to the following three assemblies:

    • System.ServiceModel

    • System.IdentityModel

    • Microsoft.IdentityModel

    • System.Configuration

  4. Create a service contract. To create a service contract, add a new class library project and then give it a name, for example, Interfaces.

  5. Add an interface file to the Interfaces project with a public interface. Give it a name, for example, IStringService.

  6. Add a reference to the System.ServiceModel assembly.

  7. Add the following declaration to the top.

    using System.ServiceModel;
    
    
  8. Add code similar to the following.

        [ServiceContract]
        public interface IStringService
        {
            [OperationContract]
            string Reverse( string value );
        }
    
    
  9. Switch back to the WCF service console application and add a reference to the Interfaces project you have just created.

  10. 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.

  11. 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 );
            }
        }
    
    
  12. 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.

  13. Add the following declarations at the top.

    using System.Collections.Generic;
    using System.IdentityModel.Tokens;
    using Microsoft.IdentityModel.Tokens;
    using Interfaces;
    
    
  14. 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." );
            }
        }
    
    
  15. Implement the service host instantiation.

  16. 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;
    
    
  17. 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;
            }
    
    
  18. 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="...update to your Access Control namespace..."/>
        <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>
    
    
  19. 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();
    
    
    
  20. 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.

  21. Update app.config with your ACS information.

  22. Test your work by pressing F5 and running the project.

  23. You should see the following message.

    StringService has been started.
    Press <ENTER>  to exit
    
    
  24. 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

  1. In Visual Studio 2010, in the existing solution create a new Windows Console Application project. Give it a name, for example WCFClient.

  2. Add a reference to the WSTrustBinding you project created earlier.

  3. Add a reference to the Interfaces project.

  4. Add a reference to the following three assemblies:

    • System.ServiceModel

    • System.IdentityModel

    • Microsoft.IdentityModel

    • System.Configuration

  5. 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;
    
    
  6. 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 ClientUsername              = 
    ConfigurationManager.AppSettings.Get( "ClientUsername" );
            static string ClientPassword              = 
    ConfigurationManager.AppSettings.Get( "ClientPassword" );
    
            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.
                stringServiceFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
                stringServiceFactory.Credentials.ServiceCertificate.DefaultCertificate = GetServiceCertificate();
    
                // Set the client credentials.
                stringServiceFactory.Credentials.UserName.UserName = ClientUsername;
                stringServiceFactory.Credentials.UserName.Password = ClientPassword;
    
                return stringServiceFactory;
            }
    
            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 );
            }
    
    
  7. 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();
    
    
  8. Add the following configuration information to the app.config file.

    <appSettings>
        <!-- ACS v2 configuration -->
        <add key="AccessControlHostName" value="accesscontrol.windows.net"/>
        <add key="AccessControlNamespace" value="...update to your Access Control namespace..."/>
    
        <!-- Service configuration -->
        <add key="ServiceAddress" value=" " />
        <add key="ServiceCertificateFilePath" value="..\..\..\Certificates\WcfServiceCertificate.cer"/>
    
        <!-- Client configuration -->
        <add key="ClientUsername" value="...update to your username..."/>
        <add key="ClientPassword" value="...update to your password..."/>
        
      </appSettings>
    
    
  9. 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

  1. 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
    
  2. 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>
    
  3. Enter an arbitrary string, for example, hello world!, and then press Enter.

  4. 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

Did you find this helpful?
(1500 characters remaining)
© 2013 Microsoft. All rights reserved.