Calling a Service Bus HTTP Endpoint with Authentication using WebClient

 

This article demonstrates using WebClient in C# to make a client call to a secure Service Bus endpoint exposed using WebHttpRelayBinding. There are two key take-aways from this that mightn’t be covered by existing documentation and samples: (1) learn how place an Access Control STS issued token into the Authorization header, and (2) make a client call to the service without the dependency on the SB client library. The second point is particularly important because non-.NET languages (e.g. PHP, Python, Java) have an analogous to WebClient, therefore understanding this example enables you to easily build clients in other non-.NET languages.

Before getting started you’ll first need to create a WCF service exposed via WebHttpRelayBinding. Below are four files you can copy-paste into your own solution and replace the values for the issuer name, key, and service namespace. The picture below illustrates where you can get the service namespace, default issuer name, and default issuer key to use out-of-the-box with these samples. The page is a screenshot of ServiceNamespace.aspx.

image

App.config

 <?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <!-- Application Binding -->
      <webHttpRelayBinding>
        <binding name="default">
          <security relayClientAuthenticationType="RelayAccessToken"></security>
        </binding>
      </webHttpRelayBinding>
    </bindings>

    <services>
      <service behaviorConfiguration="default" name="Microsoft.ServiceBus.Samples.TextService">
        <endpoint address="" behaviorConfiguration="sharedSecretClientCredentials"
          binding="webHttpRelayBinding" bindingConfiguration="default"
          name="RelayEndpoint" contract="Microsoft.ServiceBus.Samples.ITextContract" />
      </service>
    </services>

    <behaviors>
      <endpointBehaviors>
        <behavior name="sharedSecretClientCredentials">
          <transportClientEndpointBehavior credentialType="SharedSecret">
            <clientCredentials>
              <sharedSecret issuerName="owner" issuerSecret="-key-"/>
            </clientCredentials>
          </transportClientEndpointBehavior>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="default">
          <serviceDebug httpHelpPageEnabled="false" httpsHelpPageEnabled="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

 

ImageContract.cs

 namespace Microsoft.ServiceBus.Samples
{
    using System;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Web;
    using System.IO;

    [ServiceContract(Name = "TextContract", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
    public interface ITextContract
    {
        [OperationContract, WebGet]
        String GetText();
    }

    public interface IImageChannel : ITextContract, IClientChannel { }
}

ImageService.aspx

 namespace Microsoft.ServiceBus.Samples
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using Microsoft.ServiceBus.Web;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Web;

    [ServiceBehavior(Name = "TextService", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
    class TextService : ITextContract
    {
        public String GetText()
        {
            WebOperationContext.Current.OutgoingRequest.ContentType = "text/plain";
            return "Hello World";
        }
    }
}

 

Program.cs

 namespace Microsoft.ServiceBus.Samples
{
    using System;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using Microsoft.ServiceBus;
    using System.ServiceModel.Web;

    class Program
    {
        static void Main(string[] args)
        {
            string serviceNamespace = "-servicenamespace-";
            
            Uri address = ServiceBusEnvironment.CreateServiceUri("https", serviceNamespace, "Text");

            WebServiceHost host = new WebServiceHost(typeof(TextService), address);
            host.Open();

            Console.WriteLine("Copy the following address into a browser to see the image: ");
            Console.WriteLine(address + "GetText");
            Console.WriteLine();
            Console.WriteLine("Press [Enter] to exit");
            Console.ReadLine();

            host.Close();
        }
    }
}

 

Once you have the above service up-and-running creating a client using WebClient is strait forward.

 

Program.cs

 namespace Client
{
    using System;
    using Microsoft.AccessControl.Client;
    using System.Net;

    class Program
    {
        static void Main(string[] args)
        {
            string serviceNamespace = "-";
            string issuerName = "-";
            string issuerKey = "-";
            
            string baseAddress= string.Format("https://{0}.servicebus.windows.net/",serviceNamespace);
            string serviceAddress = string.Format("https://{0}.servicebus.windows.net/Text/GetText", serviceNamespace);

            TokenFactory tf = new TokenFactory(string.Format("{0}-sb",serviceNamespace), issuerName, issuerKey);
            string requestToken = tf.CreateRequestToken();
            string returnToken = tf.GetACSToken(requestToken, baseAddress);

            WebClient client = new WebClient();
            
            client.Headers[HttpRequestHeader.Authorization] = string.Format("WRAP access_token=\"{0}\"", returnToken);
            
            string returnString = client.DownloadString(new Uri(serviceAddress));
            
            Console.WriteLine(returnString);

            Console.ReadLine();
        }

    }
}

First you’ll notice that I am using TokenFactory, which comes from my “Requesting a Token from Access Control in C#”. The base address is the address of the Relying Party. This address maps to the Scope in your Access Control Service configuration. A token granted to /foo in Service Bus will give you access too /foo/bar and /foo/baz because Service Bus uses longest pre-fix matching on the path. This is why we can get a token for the root of the path and use that token to access a resource (i.e. endpoint) at it’s child address.

Also notice that the service namespace when instantiating TokenFactory is is postfixed with “-sb”. This is because the Access Control service has two independent instances of an STS running, one is at https://servicenamespace.accesscontrol.windows.net and the other at https://servicenamespace-sb.accesscontrol.windows.net/ as you can see, they only differ by “-sb”. The latter is a special STS that is just for the Service Bus. I won’t go into the details of this design decision, but from the applications perspective we only have to make sure to use the right STS for issue tokens to be used by the Service Bus.

Once the requesting token is generated by the TokenFactory it is sent to the Access Control Service STS via GetACSToken() method which returns a SWT token issued by the ACS STS.

The ACS-issued token is then added to the HTTP Authorization Header. Make sure to include “WRAP access_token=\”\”” in the header. This informs the Service Bus endpoint of the structure of the token.

F5 and you are golden.