Monday, 15 December 2014

Using basic authentication in an Azure Cloud Service

I recently had a requirement to use transport security with basic authentication in a web service hosted in Azure. Basic authentication is a mechanism for a HTTP user agent to provide credentials when making a request to the server, and is supported by all major browsers and servers. It doesn’t require cookies, session identifiers, or login pages. Instead it uses a static, standard HTTP header which means that no handshakes need to be performed.

IIS web servers provide basic authentication against Windows accounts on the server or through active directory. This situation is further complicated in services hosted in Azure. The following code shows how transport security with basic authentication can be specified in a web.config file.

1 <bindings>
2 <basicHttpsBinding>
3 <binding name="TransportSecurity">
4 <security mode="Transport">
5 <transport clientCredentialType="Basic"/>
6 </security>
7 </binding>
8 </basicHttpsBinding>
9 </bindings>

However, when you run an Azure cloud service with this configuration you’ll receive the following error message:

The authentication schemes configured on the host ('Anonymous') do not allow those configured on the binding 'BasicHttpsBinding' ('Basic').  Please ensure that the SecurityMode is set to Transport or TransportCredentialOnly.  Additionally, this may be resolved by changing the authentication schemes for this application through the IIS management tool, through the ServiceHost.Authentication.AuthenticationSchemes property, in the application configuration file at the <serviceAuthenticationManager> element, by updating the ClientCredentialType property on the binding, or by adjusting the AuthenticationScheme property on the HttpTransportBindingElement.

The initial problem is that basic authentication is unavailable by default for Azure web roles. It can be enabled either by enabling RDP on the virtual machine that the service is running on, RDPing in and adding basic authentication to IIS and then enabling it. Alternatively you could write a Powershell script to install basic authentication, and configure it to run from your VS solution when the web role starts up. Then you need to create a Windows account in the virtual machine that will be used during basic authentication.

This solution was not ideal. Moving forwards the service could have many different users, and I didn’t like the thought of having to create Windows accounts for each user. Furthermore, I’m a firm believer in trying to keep web services as provider agnostic as possible, in order to reduce problems if the service needs to be moved to another provider.

An alternative solution would be to use a different basic authentication module, provided by a third party. This is also not ideal, as it involves additional effort in identifying a suitable third party module to use, and then much time thoroughly testing it.

In this blog post I’ll outline my solution to this problem, which is to implement your own basic authentication mechanism. Basic authentication uses a simple protocol:

  1. The “username:password” format is used to combine username and password into one string.

  2. The resulting string is then base64 encoded.

  3. The encoding string is sent to the server in an Authorization header sent with the web request:
Authorization: Basic <base64 encoded username:password goes here>

Implementation

My solution is in three parts:

  1. Configure the service to use transport security but no authentication.
  2. In the service, intercept all web requests and parse out the Authorization header that contains the basic authentication credentials. The extracted credentials can then be compared against the actual credentials.
  3. In the client, intercept all web requests and add an appropriate Authorization header using the convention specified for basic authentication.

The following code shows how to configure the service to use transport security but not authentication.

1 <bindings>
2 <basicHttpsBinding>
3 <binding name="TransportSecurity">
4 <security mode="Transport">
5 <transport clientCredentialType="None"/>
6 </security>
7 </binding>
8 </basicHttpsBinding>
9 </bindings>

To intercept web requests to the service I created a class called BasicAuthenticationManager that derives from the ServiceAuthorizationManager class, which provides authorization access checking for service operations. This class overrides the CheckAccessCore method which checks authorization for the given operation context. In this method you can obtain the headers for the web request, and you can then parse out the Authorization header. The following code example shows this.

1 protected override bool CheckAccessCore(OperationContext operationContext)
2 {
3 var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
4
5 if (!string.IsNullOrWhiteSpace(authHeader))
6 {
7 if (authHeader.StartsWith("Basic"))
8 {
9 var credentials = ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(authHeader.Substring(6))).Split(':');
10
11 // Compare credentials against stored encrypted credentials
12 // If equal return true, otherwise false
13 }
14 }
15 }

The logic is straight forward: The Authorization header is extracted from the web request, and then the credentials are extracted from the Authorization header. The credentials can then be validated using your chosen approach (such as comparing them against encrypted credentials stored in configuration). If the credentials are valid, then return true. Otherwise return false, or throw the exception of your choosing to prevent the service operation being executed.

The service must then be configured to use the BasicAuthenticationManager class. This can be accomplished by adding a serviceAuthorization element to your web.config. Note that the format for specifying the class is AssemblyNamespace.Classname, AssemblyNamespace.

1 <?xml version="1.0"?>
2 <configuration>
3
4 <system.serviceModel>
5 ...
6 <behaviors>
7 <serviceBehaviors>
8 <behavior>
9 ...
10 <serviceAuthorization serviceAuthorizationManagerType="FullyQualifiedTypeName, AssemblyName" />
11 ...
12 </behavior>
13 </serviceBehaviors>
14 </behaviors>
15 </system.serviceModel>
16
17
18 </configuration>

The client that invokes the web service must then be updated to create the Authorization header for every service operation. The following code example shows this.

1 using (var client = new Proxy.Client())
2 {
3 var userName = "<username goes here>";
4 var password = "<password goes here>";
5
6 // Create the authorization header
7 var httpRequestProperty = new HttpRequestMessageProperty();
8 httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
9 Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + ":" + password));
10
11 using (new OperationContextScope(client.InnerChannel))
12 {
13 // Add the authorization header to every outgoing message
14 OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
15
16 // Make web requests
17 }
18 }


 

The HttpRequestMessageProperty class is used to provide access to the HTTP request, with the Headers property providing access to the HTTP headers from the request. It’s then easy to add an Authorization header that comprises “Basic “ and the base64 encoded “username:password” string. The OperationContextScope class is then used to add the authorization header to every outgoing message.

Summary

This blog post has demonstrated how to use basic authentication in an Azure cloud service, without having to expose the underlying virtual machine that the service runs on, and without then having to undertake messy configuration of the virtual machine. It offers more flexibility than using ISS basic authentication, as you can specify the credentials in your service, instead of having to rely upon basic authentication against a Windows account.

1 comment:

  1. David, thank you very much! This really helped

    ReplyDelete