Use basic authentication with Azure API Management

I developed the habit to unlock Azure App Services using so-called Gateway Services. As the name implies, Gateway Services are nothing more than gatekeepers. They have a fixed set of responsibilities: Give customers authorized access using basic authentication, store the posted entity in original format (Azure Storage tables for XML/Json, blobs for file attachments) and send an event message to a queue to kick off the process in a decoupled way.

I first implemented the Gateway Services via custom coding. I created a separate Web App for each customer. One customer, one Gateway. After a while I realized I could implement the Gateway Service via a Logic App using out-of-the-box API’s (so without any custom coding): Request, Azure Storage Tables and Blobs (with looping for attachments), Azure Storage Queue, Response. Fair enough. The only remaining responsibility for the Web App was to call the Logic App and apply basic authentication. The next step was to call the Logic App from Azure API Management. You can’t miss the option to import a new API from a Logic App. Nice. Now there was only one problem left. How to perform basic authentication?

When using Azure Active Directory and ADFS 3.0 you need to define an Authorization Server. You can also use a OAuth 2.0 bearer token for external identity providers like Microsoft and Google. But the Security section is not what we need here. For basic authenication, you can use an inbound policy: check-header.

[box type=”info”]
<policies>
<inbound>
<base />
<check-header name=”Authorization”
failed-check-httpcode=”401″
failed-check-error-message=”Not authorized”
ignore-case=”false”>
<value>Basic a2xhbnQ6V2Vsa29tMjAxOA==</value>
</check-header>
[/box]

The Authorization header looks quite complicated, but you can use an on-line tool or base64encode.org to generate the header. The basic authentication header is a base64 encoded string with format username:password.

When testing the API Management call to the logic app with the above policy applied, I received a rather cryptic error:
{
“error”: {
“code”: “DirectApiAuthorizationRequired”,
“message”: “The request must be authenticated only by Shared Access scheme.”
}
}

Using this excellent blog, I found out that was due to the fact that Logic Apps are not able to handle the Authorization HTTP header. So, I had to find a way to remove the Authorization header after authentication/authorization. Luckily enough that’s easy. You need to add another policy to the inbound section:

[box type=”warning”]
<set-header name=”Authorization” exists-action=”delete”/>
[/box]

WCF Authenticate Preemptively

Normally when you send a request to a service that uses basic authenication, you will initially send a service request without authorization header. The service will respond with a Http 500 error and will send the client an authentication request. This all happens automatically. The client will send the service request again. This time an authorization header goes with the request. When all things are well, the service responds with a Http 200 Success message.
Below you find the WCF code you will normally use:

string ditnopUrl = “http://dspimportservice.nl/&#8221;;
BasicHttpBinding binding = new BasicHttpBinding();
binding.Name = “Opdracht_OS_XOPBinding”;
binding.MaxReceivedMessageSize = 2147483647;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

EndpointAddress address = new EndpointAddress(ditnopUrl);
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = “user”;
loginCredentials.UserName.Password = “password”;

factory = new ChannelFactory(binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find();
factory.Endpoint.Behaviors.Remove(defaultCredentials); //remove default ones
factory.Endpoint.Behaviors.Add(loginCredentials); //add required ones
channel = factory.CreateChannel();

channel.Open();
Opdracht_OS_XOPRequest request = new Opdracht_OS_XOPRequest();
request.Opdrachtbericht = opdrachtbericht;
Opdracht_OS_XOPResponse responseDSP = channel.Opdracht_OS_XOP(request);

channel.Close();
factory.Close();

Maybe you expect you don’t have to use the code that adds the client credentials to the service behavior, but when you remove that code you will receive an error “The username is not provided. Specify username in ClientCredentials”.

Now suppose you use the same service with basic authentication, but this time you specify ‘authenticate preemptively’. This can happen if you build a non-WCF service that you host on let’s say Tomcat. In this case the service expects an authorization header on the initial request. An authentication request will not be sent. The service simply responds with a Http 500 error. So, what you need to do now, is specifically set the authorization header and send it with the request. The code below shows the code you can use:

string ditnopUrl = “http://dspimportservice.nl/&#8221;;
BasicHttpBinding binding = new BasicHttpBinding();
binding.Name = “Opdracht_OS_XOPBinding”;
binding.MaxReceivedMessageSize = 2147483647;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

EndpointAddress address = new EndpointAddress(ditnopUrl);
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = “user”;
loginCredentials.UserName.Password = “password”;

factory = new ChannelFactory(binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find();
factory.Endpoint.Behaviors.Remove(defaultCredentials); //remove default ones
factory.Endpoint.Behaviors.Add(loginCredentials); //add required ones
channel = factory.CreateChannel();

using (OperationContextScope scope = new OperationContextScope(channel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] = “Basic ” +
Convert.ToBase64String(Encoding.ASCII.GetBytes(“user : password”));
System.ServiceModel.OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;

channel.Open();
Opdracht_OS_XOPRequest request = new Opdracht_OS_XOPRequest();
request.Opdrachtbericht = opdrachtbericht;
Opdracht_OS_XOPResponse responseDSP = channel.Opdracht_OS_XOP(request);
}

channel.Close();

Add basic authentication to WCF service hosted in Azure

You can add basic authentication to your WCF service by adding a so-called HTTP module to the project with your service contract. The Http module intercepts the web service calls before they reach the actual service. The code of code file UserNameAuthenticator.cs is added at the bottom of the post. The only thing you will have to do next, is add the http module to the web.config:

<configuration>

<system.webServer>
<modules>
<add name=”BasicAuthenticationModule” type=”DSPGateway.ServiceContract.UserNameAuthenticator” />
</modules>
</system.webServer>

Code file:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;

namespace DSPGateway.ServiceContract
{
class UserNameAuthenticator : IHttpModule
{
public UserNameAuthenticator()
{
}
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
application.EndRequest += new EventHandler(this.OnEndRequest);
}
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
{
HttpApplication app = (HttpApplication)source;
//the Authorization header is checked if present
string authHeader = app.Request.Headers[“Authorization”];
if (!string.IsNullOrEmpty(authHeader))
{
string authStr = app.Request.Headers[“Authorization”];
if (authStr == null || authStr.Length == 0)
{
// No credentials; anonymous request
return;
}
authStr = authStr.Trim();
if (authStr.IndexOf(“Basic”, 0) != 0)
{
// header is not correct…we’ll pass it along and
// assume someone else will handle it
return;
}
authStr = authStr.Trim();
string encodedCredentials = authStr.Substring(6);
byte[] decodedBytes =
Convert.FromBase64String(encodedCredentials);
string s = new ASCIIEncoding().GetString(decodedBytes);
string[] userPass = s.Split(new char[] { ‘:’ });
string username = userPass[0];
string password = userPass[1];
if (username == “user” && password == “password“)
{
app.Context.User = new GenericPrincipal(new GenericIdentity(username, “DSP”), null);
}
else
{
DenyAccess(app);
return;
}
}
else
{
app.Response.StatusCode = 401;
app.Response.End();
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
//the authorization header is not present
//the status of response is set to 401 and it ended
//the end request will check if it is 401 and add
//the authentication header so the client knows
//it needs to send credentials to authenticate
if (HttpContext.Current.Response.StatusCode == 401)
{
HttpContext context = HttpContext.Current;
context.Response.StatusCode = 401;
context.Response.AddHeader(“WWW-Authenticate”, “Basic Realm”);
}
}
private void DenyAccess(HttpApplication app)
{
app.Response.StatusCode = 401;
app.Response.StatusDescription = “Access Denied”;

// Write to response stream as well, to give user visual
// indication of error during development
app.Response.Write(“401 Access Denied – invalid acccount”);
app.CompleteRequest();
}
public void Dispose()
{
}
}
}

Read More »