Exercise 3: Securing a WCF service using Windows Azure Active Directory

DZone 's Guide to

Exercise 3: Securing a WCF service using Windows Azure Active Directory

· Cloud Zone ·
Free Resource

In this exercise, we’ll secure our WCF service using Windows Azure Active Directory.


The following is required to complete this hands-on lab:

  • Microsoft Visual Studio 2013
  • The latest Windows Azure SDK for .NET
  • A Windows Azure subscription

Download the finished WPF Client and WCF Service

Previous Labs

The following is required to complete this hands-on lab:

Task 1: Creating a Windows Azure Active Directory tenant and add a user

In this task, we’ll create a Windows Azure Active Directory tenant. We’ll also add a user, which is specific to this lab. In a real environment, we would set up Active Directory synchronization so that our Azure AD instance relied on the users pushed from the on-premises instance.

  1. Log into your Azure administrative account at https://manage.windowsazure.com if not already open.

  2. In the bottom right corner, click the New button.


    Adding Azure Directory Services

  3. You will create a new active directory in the Windows Azure portal. From the portal menu, choose App Services, active directory, directory.


    Creating a new active directory

  4. You will select custom create.


    Creating a new active directory

  5. You will specify the new directory details. Type in name, domain name, and a country or region.


    Specifying directory details

  6. You will validate the newly created directory. Verify the newly created directory is visible.


    Validating the creation of a new active directory

  7. You will now add users to the new active directory. From the menu choose users.


    Adding new users

  8. You will now add a new user. From the bottom menu bar choose add user.


    Adding a new user

  9. You will enter user details. Choose new user for the type of user. Enter a user name.


    Entering user details

  10. You will complete the entering of user information. Specify the first, last, and display name. This will be an ordinary user so from them role drop-down choose user.


    Specifying user details

  11. You will now receive a temporary password. Click the create button.


    Creating a temporary password

  12. You will receive a random password. It will also type in an email address which will contain the generated password.


    Creating a temporary password

Task 2: Creating a Windows Azure Active Directory service application

  1. You will now associate an application with the directory services you just created. From your directory services detail pane choose applications then add an app.


    Adding an application for your directory services

  2. From the menu choose add an application my organization is developing.


    Adding an organizational application

  3. Provide a name for your application. Verify that the type is a web application or web API application.


    Naming your application

  4. You will verify your application properties. Note the sign-on URL as well as the app ID URI.


    Validating application properties

  5. You will enable users to sign into your application. Choose enable users to sign on.


    Enabling sign-on for users

  6. You will copy the app ID URI to the clipboard. Also take note of the Federation meta data document URL.


    Collecting application information from the portal

Task 2: Modifying the WCF Service

You will return back to the WCF service created in the previous steps. Start Visual Studio as administrator and open the project. The download link is provided at the beginning of this post.

  1. You will now add a class to help manage the tokens in your application. Right mouse click on App_Code, then add, followed by class. Name the class module BearerTokenMessageInspector.cs. Also note the code snippet below.


    Adding a new class

  2. Open the class module just created. Paste in the code from the code snippet.


    Pasting in the code for BearerTokenMessageInspector.cs

    (Code Snippet - BearerTokenMessageInspector.cs)

// BearerTokenMessageInspector.cs 
// Windows Azure Active Directory Helper
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IdentityModel.Metadata;
using System.IdentityModel.Selectors;
using System.IdentityModel.Services;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Security;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml;

namespace Expenses.WcfService

     public class BearerTokenMessageInspector : IDispatchMessageInspector
          // You need to use what you entered
          // at the portal. You audience and authority
          // will be different.
          const string audience = "http://brunoexpenseswcf.azurewebsites.net";
          const string authority = "https://login.windows.net/expensesdomain.onmicrosoft.com";

          static string _issuer = string.Empty;
          static List<X509SecurityToken> _signingTokens = null;
          static DateTime _stsMetadataRetrievalTime = DateTime.MinValue;
          static string scopeClaimType = "http://schemas.microsoft.com/identity/claims/scope";

          public BearerTokenMessageInspector()

          public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
                object correlationState = null;

                HttpRequestMessageProperty requestMessage = request.Properties["httpRequest"] as HttpRequestMessageProperty;
                if (request == null)
                     throw new InvalidOperationException("Invalid request type.");
                string authHeader = requestMessage.Headers["Authorization"];

                if (string.IsNullOrEmpty(authHeader) || !this.Authenticate(authHeader))
                     WcfErrorResponseData error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Bearer authorization_uri=\"" + authority + "\"" + "," + "resource_id=" + audience));
                     correlationState = error;

                return correlationState;

          private bool Authenticate(string authHeader)
                const string bearer = "Bearer ";
                if (!authHeader.StartsWith(bearer, StringComparison.InvariantCultureIgnoreCase)) { return false; }

                string jwtToken = authHeader.Substring(bearer.Length);
                string issuer;
                string stsMetadataAddress = string.Format("{0}/federationmetadata/2007-06/federationmetadata.xml", authority);
                List<X509SecurityToken> signingTokens;

                // Get tenant information that's used to validate incoming jwt tokens
                GetTenantInformation(stsMetadataAddress, out issuer, out signingTokens);

                JwtSecurityTokenHandler tokenHandler =
                     new JwtSecurityTokenHandler()
                          // For demo purposes certificate validation is turned off. Please note that this shouldn't be done in production code.
                          CertificateValidator = X509CertificateValidator.None

                TokenValidationParameters validationParameters =
                     new TokenValidationParameters()
                          //AllowedAudience = audience,
                          ValidIssuer = issuer
                          //SigningTokens = signingTokens,

                // Validate token
                ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters);

                // Set the ClaimsPrincipal on the current thread.
                Thread.CurrentPrincipal = claimsPrincipal;

                // Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
                if (HttpContext.Current != null)
                     HttpContext.Current.User = claimsPrincipal;

                // if the token is scoped, verify that required permission is set in the scope claim
                if ((ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null) && (ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation"))
                     return false;

                return true;

          /// <summary>
          /// Parses the federation metadata document and gets issuer Name and Signing Certificates
          /// </summary>
          /// <param name="metadataAddress">URL of the Federation Metadata document</param>
          /// <param name="issuer">Issuer Name</param>
          /// <param name="signingTokens">Signing Certificates in the form of X509SecurityToken</param>
          static void GetTenantInformation(string metadataAddress, out string issuer, out List<X509SecurityToken> signingTokens)
                signingTokens = new List<X509SecurityToken>();

                // The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.            
                if ((DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24)
                     || string.IsNullOrEmpty(_issuer)
                     || _signingTokens == null)
                     MetadataSerializer serializer = new MetadataSerializer()
                          // turning off certificate validation for demo. Don't use this in production code.
                          CertificateValidationMode = X509CertificateValidationMode.None
                     MetadataBase metadata = serializer.ReadMetadata(XmlReader.Create(metadataAddress));

                     EntityDescriptor entityDescriptor = (EntityDescriptor)metadata;

                     // get the issuer name
                     if (!string.IsNullOrWhiteSpace(entityDescriptor.EntityId.Id))
                          _issuer = entityDescriptor.EntityId.Id;

                     // get the signing certs
                     _signingTokens = ReadSigningCertsFromMetadata(entityDescriptor);

                     _stsMetadataRetrievalTime = DateTime.UtcNow;

                issuer = _issuer;
                signingTokens = _signingTokens;

          static List<X509SecurityToken> ReadSigningCertsFromMetadata(EntityDescriptor entityDescriptor)
                List<X509SecurityToken> stsSigningTokens = new List<X509SecurityToken>();

                SecurityTokenServiceDescriptor stsd = entityDescriptor.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>().First();

                if (stsd != null)
                     IEnumerable<X509RawDataKeyIdentifierClause> x509DataClauses = stsd.Keys.Where(key => key.KeyInfo != null && (key.Use == KeyType.Signing || key.Use == KeyType.Unspecified)).
                                                                                 Select(key => key.KeyInfo.OfType<X509RawDataKeyIdentifierClause>().First());

                     stsSigningTokens.AddRange(x509DataClauses.Select(token => new X509SecurityToken(new X509Certificate2(token.GetX509RawData()))));
                     throw new InvalidOperationException("There is no RoleDescriptor of type SecurityTokenServiceType in the metadata");

                return stsSigningTokens;

          public void BeforeSendReply(ref Message reply, object correlationState)
                WcfErrorResponseData error = correlationState as WcfErrorResponseData;
                if (error != null)
                     HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
                     reply.Properties["httpResponse"] = responseProperty;
                     responseProperty.StatusCode = error.StatusCode;

                     IList<KeyValuePair<string, string>> headers = error.Headers;
                     if (headers != null)
                          for (int i = 0; i < headers.Count; i++)
                                responseProperty.Headers.Add(headers[i].Key, headers[i].Value);

     public class BearerTokenServiceBehavior : IServiceBehavior
          public BearerTokenServiceBehavior()


          public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
                // no-op

          public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
                foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
                     foreach (EndpointDispatcher epDisp in chDisp.Endpoints)
                          epDisp.DispatchRuntime.MessageInspectors.Add(new BearerTokenMessageInspector());

          public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
                // no-op

     public class BearerTokenExtensionElement : BehaviorExtensionElement
          public override Type BehaviorType
                get { return typeof(BearerTokenServiceBehavior); }

          protected override object CreateBehavior()
                return new BearerTokenServiceBehavior();

     internal class WcfErrorResponseData
          public WcfErrorResponseData(HttpStatusCode status) :
                this(status, string.Empty, new KeyValuePair<string, string>[0])
          public WcfErrorResponseData(HttpStatusCode status, string body) :
                this(status, body, new KeyValuePair<string, string>[0])
          public WcfErrorResponseData(HttpStatusCode status, string body, params KeyValuePair<string, string>[] headers)
                StatusCode = status;
                Body = body;
                Headers = headers;

          public HttpStatusCode StatusCode
                private set;

          public string Body
                private set;

          public IList<KeyValuePair<string, string>> Headers
                private set;
  1. Copying all the code in BearerTokenMessageInspector.cs

  2. You will modify the audience and authority strings to reflect the information from the portal. Your URLs will differ.


    Modifying BearerTokenMessageInspector.cs with information from the portal

  3. You will now enable Javascript web tokens. From the tools menu choose library package manager, then package manager console.


    Starting package manager console

  4. Type in the following command into package manager console. Take special note that this is version 1.

    Install-Package System.IdentityModel.Tokens.Jwt -version 1.0.0


    Installing the identity model package for JavaScript web tokens version 1

  5. You will need to add references. Right mouse click on the references and choose add reference.


    Adding references

  6. You will add two references. The first reference is seen below. Make sure it is checked. Click OK to continue.


    Adding a reference

  7. You will now add a second reference as seen below.


    Adding a reference

  8. Open the web.config file and add the following entries. Notice that we have both behavior and behavior extensions.


    Modifying web.config to implement authentication

    (Code Snippet - Web.config in Expeneses.WCFServcie)

<?xml version="1.0"?>
        connectionString="Server=tcp:uxpwgfs4g6.database.windows.net,1433;Database=Expenses;User ID=azureuser@uxpwgfs4g6;Password=MyP@ssw0rd;       Trusted_Connection=False;Encrypt=True;Connection Timeout=30;"
     <customErrors mode="Off"/>
          <add assembly="System.Security, Version=, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
          <add assembly="System.Data.Linq, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
          <add assembly="System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
          <add assembly="System.Net.Http, Version=, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
     <httpRuntime targetFramework="4.5"/>

             <!-- Add this line below. Ignore errors ->

             <!--To avoid disclosing metadata information, set the values below to false before deployment -->
             <!--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="true"/>
      <!-- Add this extensions section below. ->

             type="Expenses.WcfService.BearerTokenExtensionElement, App_Code"/>

     <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"/>
  1. You are now ready to publish the updated WCF Service. Right mouse click as seen below and choose publish website.


    Publishing the website (the WCF service)

  2. Publish the service. Click the publish button.


    Publishing the service

  3. View the output window to verify correct publishing.


    Verifying the publish

Task 3: Running the WPF client to test the service

You will return back to the WPF client app from previous steps. Start Visual Studio as administrator and open the project. The download link for this project is provided at the beginning of this post.

  1. Return back to the WPF application (client app) to verify that the service is no longer available without a proper login and authentication.


    Testing the WCF service by running the WPF application

  2. You will notice that now we have a runtime error for the WPF application. We will need to make more modifications to avoid the failed attempt to connect to the WCF service.


    Running into runtime errors


Are many things going on with this lab.

  • You provisioned a new directory services from the Azure portal

  • You added a user to the directory services

  • You associated the WCF service with the directory services and the portal

  • You added some authentication code and the WCF service

  • You re-published the service back up to Azure websites

  • You re-tested the WPF client to verify it could not access the services because it lacks proper authentication


Published at DZone with permission of Bruno Terkaly , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}