A View Inside My Head

Jason's Random Thoughts of Interest

NAVIGATION - SEARCH

Windows Azure platform AppFabric Access Control: Obtaining Tokens

In this, the third part of a series of articles examining Windows Azure platform AppFabric Access Control (Part 1, Part2), I will continue developing the [somewhat contrived] nightclub web service scenario and demonstrate how to obtain a token from ACS that can be used to call into a Bartender web service.

A Quick Review

Our distributed nightclub system has four major components: A Customer application coupled with some external identity management system, a Bartender web service, and ACS, which acts as our nightclub’s Bouncer.

A Customer application ultimately represents a user that wishes to order a drink from the Bartender web service.  The Bartender web service must not serve drinks to underage patrons, so it needs to know the user’s age (this is known as a “claim”).  The nightclub has no interest in maintaining a comprehensive list of customers and their ages, so instead, a Bouncer ensures that the user’s claimed age comes from a trusted source (this is known as an “issuer”).  If the claim’s source can be verified, the Bouncer will give the customer a token containing its own set of claims that the Bartender web service will recognize.  In the end, the Bartender doesn’t have to concern itself with all of the possible issuers – it only needs to recognize a single token generated by the Bouncer (ACS).

separateservice_issuer

 

Anatomy of a Token Request

ACS uses a subset of the OAuth Web Resource Authorization Protocol (WRAP) to request tokens.  The tokens themselves use the Simple Web Token (SWT) format.  The latest specification documents for both WRAP and SWT can be found in the Files section of the “OAuth WRAP WG” Google Group: http://groups.google.com/group/oauth-wrap-wg/files

The power of ACS derives from the simplicity of WRAP/SWT.  All of the token requests are performed as regular HTTP POSTs, and the response is a URL Encoded string of name/value pairs (one of those being the token itself).  Even if your Issuer (Identity Management System) does not natively know how to request a token from ACS, which is very likely, then it is easy to either build this into the application itself, or write a small service to perform this work. 

At the HTTP protocol level, a token request may resemble:

REQUEST:

POST https://jasondemo.accesscontrol.windows.net/WRAPv0.9/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: jasondemo.accesscontrol.windows.net
Content-Length: 155
Expect: 100-continue

wrap_name=Ohio&wrap_password=LVMjImkJjIBDrJHbTzyrioeajIFpV27tW2uTuCCOYFY%3d
&wrap_scope=http%3a%2f%2fmyserver.domain%2fBartender&DOB=1979-05-25T00%3a00%3a00

 

RESPONSE:

HTTP/1.1 200 OK
Content-Length: 315
Content-Type: application/x-www-form-urlencoded
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: ca165b72-5d9f-4196-9272-1b80c8c3e02a
Date: Mon, 22 Mar 2010 13:26:44 GMT

wrap_access_token=Birthdate%3d1979-05-25T00%253a00%253a00%26Issuer%3dhttps%253a%252f%252f
jasondemo.accesscontrol.windows.net%252f%26Audience%3dhttp%253a%252f%252fmyserver.domain%
252fBartender%26ExpiresOn%3d1269307605%26HMACSHA256%3deSgT1Gwx3H6owK8hBR82ixG0DRTgM2osI3v
7NdJLQwY%253d&wrap_access_token_expires_in=43200

 

The response displayed here contains two name/value pairs: wrap_access_token and wrap_access_token_expires_in.  If the token request was invalid, then a HTTP 401 status would have been returned instead of a 200.

Notice that “wrap_access_token_expires_in” contains the value 43200, which is the number of seconds that we defined in the Token Lifetime field of the “BouncerPolicy” token policy (in Part 2).  This value is provided as a convenience to indicate how long the token will be valid.  Taking a cue from systems that use lease lifetimes, like DHCP, it might be a good practice to acquire another token at 50% of the Token Lifetime (21600 seconds in this case, or 6 hours). 

As the name suggests, “wrap_access_token” contains the SWT token itself.  This is a URL Encoded set of name/value pairs that also includes a HMACSHA256 signature to prevent the token from being tampered with.  This token does not need to be parsed by the client application; it can be treated as a magic string.  However, there is nothing special about its contents:

Birthdate=1979-05-25T00%3a00%3a00 &Issuer=https%3a%2f%2fjasondemo.accesscontrol.windows.net%2f &Audience=http%3a%2f%2fmyserver.domain%2fBartender &ExpiresOn=1269309326 &HMACSHA256=NLmcGYITc8aWvhNp6Ge54TGJxhJCVX4Q67DDLBKoIy4%3d

 

“Birthdate” is the result of the Passthrough Claim Mapping (Part 2) that took the inbound claim called “DOB” and mapped it to this outbound claim called “Birthdate”.  ACS has no idea that this is a date – it just knows that it’s a string of characters that will be understood by the destination.  The other claims that are present in this SWT token (Issuer, Audience, ExpiresOn, and HMACSHA256) are all system claim types that are reserved for use by SWT. 

Note the value of “Issuer” in this token.  In our Request, the stated Issuer was “Ohio”, which was a trusted Issuer that we defined when configuring the Service Namespace using AcmBrowser.  The Issuer of this SWT token is now the Service Namespace.  Our Bartender web service has no direct trust with the Ohio Issuer, but it will trust tokens signed by the Service Namespace (which trusts the Ohio Issuer).  Just as ACS can trust multiple Issuers, it’s also possible that the Bartender web service could trust multiple ACS Issuers (maintaining a symmetric key for each one to verify the HMACSHA256 signature).  There is a lot of flexibility in how ACS can be incorporated into your own system’s architecture.

Implementation

Because the “Ohio” issuer was created in ACS using a Symmetric 256-bit key, we have two choices for how to use this key in order to request a token from ACS.  We could send the key itself in plaintext, sort of like a password (in fact, this uses a WRAP profile that is intended for username/password authentication).  Alternatively, we could create a SWT of our own, using the key to sign it, and then send that token to ACS as part of the token request.

A simple WRAP request that provides the Issuer’s name and password (symmetric key) in plaintext would contain the following name/value pairs:

  • wrap_name (Issuer Name, as defined in the Issuer definition in ACS)
  • wrap_password (Current Key, as defined in the Issuer definition in ACS)
  • wrap_scope (Applies To, as defined in the Scope definition)
  • additional claims (will be processed by the defined Scope Rules for claim mapping)

private static string RequestTokenFromACS_usingPassword(DateTime dob) { var values = new NameValueCollection(); values.Add("wrap_name", "Ohio"); values.Add("wrap_password", "LVMjImkJjIBDrJHbTzyrioeajIFpV27tW2uTuCCOYFY="); values.Add("wrap_scope", "http://myserver.domain/Bartender"); values.Add("DOB", dob.ToString("s")); var client = new WebClient { BaseAddress = "https://jasondemo.accesscontrol.windows.net" }; return Encoding.UTF8.GetString(client.UploadValues("WRAPv0.9", "POST", values)); }


Using a SWT within a WRAP request is just as simple, but the difficulty is in creating the SWT itself.  The WRAP request would contain the following name/value pairs:

  • wrap_assertion_format (SWT in this case)
  • wrap_assertion (the SWT token that we generate and sign using the Current Key)
  • wrap_scope (Applies To, as defined in the Scope definition)

The SWT token itself would contain the following name/value pairs:

  • Issuer (Issuer Name, as defined in the Issuer definition in ACS)
  • Audience (STS Endpoint, as listed on the Service Namespace overview page)
  • ExpiresOn (integer containing the number of seconds after the Epoch date of January 1, 1970 00:00 UTC that the token will expire)
  • additional claims (will be processed by the defined Scope Rules for claim mapping)
  • HMACSHA256 (URL Encoded Base64 signature of the unsigned token)

private static string RequestTokenFromACS_usingSWT(DateTime dob) { string requestToken = GetRequestToken(dob); var values = new NameValueCollection(); values.Add("wrap_assertion_format", "SWT"); values.Add("wrap_assertion", requestToken); values.Add("wrap_scope", "http://myserver.domain/Bartender"); var client = new WebClient { BaseAddress = "https://jasondemo.accesscontrol.windows.net" }; return Encoding.UTF8.GetString(client.UploadValues("WRAPv0.9", "POST", values)); } private static string GetRequestToken(DateTime dob) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("DOB={0}&", dob.ToString("s")); sb.Append("Issuer=Ohio&"); sb.Append("Audience=https://jasondemo.accesscontrol.windows.net/WRAPv0.9&"); sb.AppendFormat("ExpiresOn={0:0}", (DateTime.UtcNow.AddMinutes(10) - new DateTime(1970, 1, 1)).TotalSeconds); return AddSignature(sb.ToString()); } private static string AddSignature(string unsignedToken) { HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String("LVMjImkJjIBDrJHbTzyrioeajIFpV27tW2uTuCCOYFY=")); var sig = hmac.ComputeHash(Encoding.ASCII.GetBytes(unsignedToken)); string signedToken = String.Format("{0}&HMACSHA256={1}",  unsignedToken,  HttpUtility.UrlEncode(Convert.ToBase64String(sig))); return signedToken; }

Where to Execute this Code

The code to request a token can easily be implemented within an application that uses that token to call into a protected resource.  In the nightclub example, this code could be incorporated right into the Customer application.  However, this would mean that the claim (Birthdate) would originate from the application itself.  It also means that the application would need to have access to the Issuer key, making it possible for a nefarious user to obtain the key and bypass the application to directly access the protected resource.  These may not be concerns for your specific system, though.

A better solution for the nightclub example, which tries to mimic the real-life State ID system, would be to create a service for each Issuer that their Customer applications call into.  This service would then be able to obtain claim information from a source of authority (such as a LDAP directory), obtain a token from ACS, and then return that token to the Customer application.  The Customer application would never communicate with ACS directly, so there would be no reason for the application to have the Issuer key (making it harder for that nefarious user to discover the key).

From Here

This article demonstrated how to obtain a token from ACS.  In the next article, I will show how to use this token in order to access a protected resource (such as our Bartender web service) and how to validate a token when a protected resource receives one. 

(Continued in Part 4)

Further Reading: