A Future Claims-Based Approach

0
270

In the future, Tailspin is considering replacing the simple user name and password authentication scheme with a claims-based approach. One option is to use Simple Web Token (SWT) and the Open Authentication (OAuth) 2.0 protocol. This approach offers the following benefits:

  • The authentication process is managed externally from the Tailspin Surveys application.
  • The authentication process uses established standards.
  • The Surveys application can use a claims-based approach to handle any future authorization requirements.

Figure 2 illustrates this approach, showing the external token issuer.

An authentication approach using SWT for the Surveys web service

In this scenario, before the mobile client application invokes a Surveys web service, it must obtain an SWT. It does this by sending a request to a token issuer that can issue SWTs; for example, Windows Azure Access Control Services (ACS). The request includes the items of information described in the following table.

Windows Azure Access Control Services

The client ID and client secret enable the issuer to determine which application is requesting an SWT. The issuer uses the user name and password to authenticate the user.

The token issuer then constructs an SWT containing the user’s identity and any other claims that the consumer application (Tailspin Surveys) might require. The issuer also attaches a hash value generated using a secret key shared with the Tailspin Surveys service.

When the client application requests data from the Surveys service, it attaches the SWT to the request in the request’s authorization header.

When the Surveys service receives the request, a custom authentication module extracts the SWT from the authorization header, validates the SWT, and then extracts the claims from the SWT. The Surveys service can then use the claims with its authorization rules to determine what data, if any, it should return to the user.

The validation of the SWT in the custom authentication module performs the following steps.

  • It verifies the hash of the SWT by using the shared secret key. This enables the Surveys service to verify the data integrity and the authenticity of the message.
  • It verifies that the SWT has not expired. The token issuer sets the expiration time when it creates the SWT.
  • It checks that the issuer that created the SWT is an issuer that the service is configured to trust.
  • It checks that the client application that is making the request is a client that the service is configured to trust.

Inside the Implementation

Now is a good time to walk through the code that implements the authentication process in more detail. As you go through this section, you may want to download the Visual Studio solution for the Tailspin Surveys application from CodePlex (http://go.microsoft.com/fwlink/?LinkId=205602).

The CustomServiceHostFactory class in the TailSpin.Services. Surveys project initializes the Surveys service. The following code example shows how this factory class creates the authorization manager.

C#
public class CustomServiceHostFactory : WebServiceHostFactory
{
private readonly IUnityContainer container;
public CustomServiceHostFactory(IUnityContainer container)
{
this.container = container;
}
protected override ServiceHost CreateServiceHost(
Type serviceType, Uri[] baseAddresses)
{
var host = new CustomServiceHost(
serviceType, baseAddresses, this.container);
host.Authorization.ServiceAuthorizationManager =
new SimulatedWebServiceAuthorizationManager();
host.Authorization.PrincipalPermissionMode =
PrincipalPermissionMode.Custom;
return host;
}
}

 

Note: The sample Surveys application uses a simulated authorization manager. You must replace this with a real authorization manager in a production application.

 

The following code example from the SimulatedWebServiceAuthorizationManager class shows how to override the CheckAccess- Core method in the ServiceAuthorizationManager class to provide a custom authorization decision.

C#
protected override bool CheckAccessCore(
OperationContext operationContext)
{
try
{
if (WebOperationContext.Current != null)
{
var headers =
WebOperationContext.Current.IncomingRequest.Headers;
if (headers != null)
{
var authorizationHeader =
headers[HttpRequestHeader.Authorization];
if (!string.IsNullOrEmpty(authorizationHeader))
{
if (authorizationHeader.StartsWith(“user”))
{
var userRegex = new Regex(@”(w+):([^s]+)”,
RegexOptions.Singleline);
var username = userRegex.Match(authorizationHeader)
.Groups[1].Value;
var password = userRegex.Match(authorizationHeader)
.Groups[2].Value;
if (ValidateUserAndPassword(username, password))
{
var identity = new ClaimsIdentity(
new[]
{
new Claim(System.IdentityModel.Claims
.ClaimTypes.Name, username)
},
“TailSpin”);
var principal =
ClaimsPrincipal.CreateFromIdentity(identity);
operationContext.ServiceSecurityContext
.AuthorizationContext.Properties[“Principal”] =
principal;
return true;
}
}
}
}
}
}
catch (Exception)
{
if (WebOperationContext.Current != null)
{
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
}
return false;
}
if (WebOperationContext.Current != null)
{
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
}
return false;
}

In this simulated authorization manager class, the CheckAccess Core method extracts the user name and password from the authorization header, calls a validation routine, and if the validation routine  succeeds, it attaches a ClaimsPrincipal object to the web service context.

In the sample application, the validation routine does nothing more than check that the user name is one of several hard-coded values.

The following code example shows how the RequestTo method in the HttpClient class adds the authorization header with the user name and password credentials to the HTTP request that the mobile client sends to the various Tailspin web services.

C#
public static HttpWebRequest RequestTo(
Uri surveysUri, string userName, string password)
{
var request = (HttpWebRequest)WebRequest.Create(surveysUri);
var authHeader = string.Format(CultureInfo.InvariantCulture,
“user {0}:{1}”, userName, password);
request.Headers[HttpRequestHeader.Authorization] = authHeader;
return request;
}