Configuring Access Control for Jupiter Motors
It's now time to
configure Access Control for the Jupiter Motors client application. The
first thing we need to do is plan who we want to access our web service,
and what actions can be performed. For this web service offered by
Jupiter Motors, it's pretty simple only our client application should
have access, and it can call any of the three functions in the service.
Just as a reminder, the functions are:
LoadStartupData: The function
returns two datasets, one containing the possible statuses for an order
and the other containing the orders waiting completion
GetStatusForOrder: This function returns the status of a selected order
AddOrderStatusUpdateToQueue: This function puts the status update into a queue to be processed by our worker role
We need to create a ruleset
allowing access to these functions, as well as the token policies and
scopes. As we're providing access to internally developed applications,
we can use the simple symmetric key functionality, similar to a user ID
and password. Additionally, we need to enable an SSL on our portal to
secure the data being transferred.
Configuring Azure AppFabric Portal
Before any requests can be made
of Azure AppFabric, we need to obtain the Management Key. This key is
included in the corresponding request to prove we are able to make the
request by having secret information. This key should be protected
carefully. To find the Management Key, we need to log in to the Azure
AppFabric portal at http://appfabric.azure.com/.
After we've logged in, we can see details of our project and the
Service Namespaces, if there are any, as shown in the following
screenshot:
As we have an existing
Service Namespace, we can click on its name to be taken to the Service
Namespace details page. The top section, named Manage, contains both the Current Management Key and Previous Management Key. Both keys are supported in case any key is changed before all existing applications are updated.
If there is no Service Namespace
listed, or if we need to create a new Service Namespace, there is a link
on the project summary page (shown in the screenshot prior to the
preceding one) that we use to create a new Service Namespace. To create a
Service Namespace, we follow these steps:
1.
The first step in creating a new Service Namespace is to choose the
name we want to use to refer to the namespace. As the name we choose
must be unique across all of Azure, it's important to validate it.
2.
The next step is to choose the region in which our Service Namespace is
to be run. At the time of writing, there were only four regions to
choose from. These regions do not refer to a particular data center, but
to a geographic region in which one or more data centers are located.
3.
Finally, if we're planning to use the Service Bus, we can choose the
number of connections. We'll discuss more about this in the further
sections, but we will configure the connections at the same time.
Configuration tools
At the time of writing, there is
no way to configure Access Control via the portal. All configuration
steps must be performed using REST calls to the configuration service
from client tools. Fortunately, the Windows Azure AppFabric SDK
(download from http://go.microsoft.com/fwlink/?LinkID=129448) includes a tool called ACM.exe that is used to configure Access Control. A full reference for ACM.EXE can be found at http://msdn.microsoft.com/en-us/library/ee706706.aspx. The C# source code is also included in case we need a reference, or we want to develop our own configuration tool.
An additional tool called AcmBrowser is provided in the AppFabric samples. The samples can be downloaded from http://go.microsoft.com/fwlink/?LinkID=129448
and installed separately from the SDK. AcmBrowser is a GUI tool that
can be used to configure Access Control, and display the configuration
in a more user-friendly manner than Acm.exe. The AcmBrowser project is
located at<installfolder>\AccessControl\ExploringFeatures\Management\AcmBrowser\ManagementBrowser.
In order to use AcmBrowser, we have to open the project and compile the
tool. At the time of writing, Acm.exe is the recommended tool to use,
and it provides useful output, so we'll use it ourselves.
The acm.exe syntax is standard verb-noun:
acm.exe <command> <resource< [-option:<option value>]
If the only option is "-?", help for that command/resource will be returned. For example, Tools>acm.exe -? returns the following help information:
More detailed help is also available for individual commands:
For a complete overview of the commands, resources, and options, the MSDN documentation is available at http://msdn.microsoft.com/en-us/library/ee706706.aspx.
Some of the options we need to include with every operation toward
Access Control are the Host, the Service Namespace, and the Management
Key. Looking once again at the management key shown above, we surely
don't want to have to type that too many times! Fortunately, we can add
these three values to the config files and omit them from the command
line.
The Acm.exe and acm.exe.config files are installed by default at C:\Program Files\Windows Azure platform AppFabric SDK\V1.0\Tools. We can edit the config file to provide the three options. The host is defaulted to the management web service address, accesscontrol.windows.net,
and should not be changed. We can copy and paste the values for the
Service Namespace and Management Key from the Azure AppFabric portal,
creating the file shown here:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="host" value="accesscontrol.windows.net"/>
<add key="service" value="jupiter"/>
<add key="mgmtkey" value="xr5BwkhSynQ+RwGbxGEkDUepB+zrF9p8qimhfqmCPZ0="/>
</appSettings>
</configuration>
At this point in the configuration process, we have a Service Namespace that does not yet have a Service Policy.
Referring to the diagram under the Basics of Access Control configuration section, we can see that we have Rule Sets, Token Policies, and Scopes to configure to create a Service Policy.
Two of the most useful commands in Acm.exe are get and getall. We use get to retrieve information on a particular Scope/Token Policy/Rule/Issuer, and we use the getall command to retrieve a list of all the Scopes/Token Policies/Rules/Issuers. The ID of the object is needed for the get command. So, as we add objects to Access Control, it's a good idea to record the details.
To confirm we don't have a scope in our Service Policy, we can use the getall command:
Tools>acm.exe getall scope
The count of the scopes in our Service Policy will be shown in the output:
The count of 0 confirms we do not have any scopes. We can repeat the process for Rules, Token Policies, and Issuers.
Creating a Token Policy
A Token Policy dictates
the lifetime of a token (to help prevent it from being reused) and
whether to autogenerate a signing key or use a static key. The command
to create a Token Policy is:
acm.exe create tokenpolicy
By adding the "-?" option, we can list all options available for this command, as shown here:
Recall that the Service,
Host, and Management Key are already set in the configuration file. All
we need to do to create a Token Policy is to set the name and key
value:
Tools>acm.exe create tokenpolicy -name:Delivery –autogeneratekey
If our Token Policy is created successfully, the ID of the policy will be returned.
Object created successfully (ID:' tp_4ef493f23daf444ca50410a7d1852125')
When using the
autogeneratekey option, the timeout is defaulted to 8 hours. If we were
using a static key, we'd specify the key value in the command (type
carefully!). For applications used in public environments, we would
probably want to set the timeout to a shorter value. Note that the ID
begins with tp_. This differentiates the Token Policy IDs from other Access Control IDs.
It's a good idea to store the
information for each object we create in a password keeper or database
for future reference. We'll need the IDs for the next configuration
steps and in some of our applications.
If we need to retrieve the details of our Token Policy, we can again use the getall command to retrieve the details of all Token Policies:
Tools>acm.exe getall tokenpolicy
We can see the ID of our Token Policy, as well as the friendly name, the timeout, and the signing key issued to our request:
Count: 1
id: tp_4ef493f23daf444ca50410a7d1852125
name: Delivery
timeout: 28800
key: l4K+qOU9OTo1dG3DWxluH+eTvsX/CBHhFbxLfxZcjC4=
Let's see how the get command works, specifying the ID as part of the options:
Tools>acm.exe get tokenpolicy
-id:tp_4ef493f23daf444ca50410a7d1852125
The following information is returned:
id: tp_4ef493f23daf444ca50410a7d1852125
name: Delivery
timeout: 28800
key: l4K+qOU9OTo1dG3DWxluH+eTvsX/CBHhFbxLfxZcjC4=
The information returned is the same as with the getall command. So far, so good now we need to create a scope, an issuer, and a rule.
Configuring a Scope
A scope groups the rules and
token policies as they relate to a specific URI. In order to create a
scope, we need to include the Token Policy ID we want associated with
it.
Tools>acm.exe create scope -name:DeliveryScope
-appliesto:http://jupitermotors.com/deliveryservice
If our scope creation is successful, the ID of the scope is returned.
Object created successfully (ID:'scp_334a7c77845e7ac20764300da9119c434ffc
c65d')
The IDs for scopes begin with scp_, just as the IDs for token policies began with tp_.
We now have a scope, tied to a URI and with a token policy. We need
this ID to create a rule, so it's a good idea to copy and paste this ID.
It's now time to create an issuer and a rule.
Configuring an Issuer
An Issuer
is another name for an Identity Provider. Users create accounts with
Identity Providers, and Identity Providers issue claims to consuming
services. In the current scenario, Access Control is the identity
consumer. We establish a trust between Access Control and the Issuer
with a secret key provided by the Issuer. The ultimate claims consumer
our local application isn't concerned with anything other than a trust
relationship with Access Control.
Tools>acm.exe create issuer -name:jupiter -issuername:jupiter -autogeneratekey
If our issuer is created
successfully, the ID is returned. As with all the other IDs we've
created, Issuer IDs begin with a distinct prefix. We'll use this ID to
create a rule allowing access to applications presenting the correct
key, so be sure to copy and paste it, too.
Configuring a Rule
Rules are where the magic
happens. We'll use Rules to map claims, and we use rules to configure
trusts with Issuers. The mapping is done with the inclaimtype, inclaimvalue, outclaimtype, and outclaimvalue options.
When we create a Rule, we need
to include a Scope ID and an Issuer ID. Because the IDs are long, it
might be easiest to build the command in a text editor first, and then
copy the command to the command window.
Tools>acm.exe create rule -name:jupiterrule1 -scopeid:scp_334a7c77845e7ac2076430
0da9119c434ffcc65d -inclaimissuerid:iss_a2f7fcb5afeb7983ffbb6ce3d1a7e91edf321350
On success, the rule ID is returned, which begins with rul_.
Object created successfully (ID:'rul_42db52d7749770ca2f585ddc1b992adecb8b76bf93f
We have now created a rule
that says anyone who presents a valid key from the "jupiter" issuer is
placed in the user role. As we add additional identity providers to our
service policy, we need to perform only these four configuration steps
to map visitors into the "users" role.
Configuring a client application for Access Control
When a service is
secured by Access Control, a client does not make its first request
directly to the service. Instead, we need to perform the following
steps:
1. Request a token from Access Control.
2. Split the token out of the response from Access Control.
3. Build the service request, including the necessary parameters and the token.
4. Issue the service request.
5. Receive and process the response from the service.
A client can cache the token
and use it until the token expires. Once a token expires, the client
application must request a new token before it can make any additional
requests from the service.
In order to request a token
from Access Control, we need to know the Service Namespace, the scope,
and the issuer key. Because these values may change (especially the
issuer key), it's advisable to place them in the application settings or
our client application:
Requesting the Token
The first modification to our code is we need to add two Imports statements:
Imports System.Net
Imports System.Collections.Specialized
We now create a POST
request to Access Control, and pass the IssuerKey and Scope settings we
defined earlier. The response is returned as a byte array, which we
then convert to a UTF8 encoded string:
Dim _client As New WebClient()
_client.BaseAddress = String.Format("https://{0}.accesscontrol.windows.net/", My.Settings.ServiceNamespace)
Dim _values As New NameValueCollection()
_values.Add("wrap_name", "wcfauthmanager")
_values.Add("wrap_password", My.Settings.IssuerKey)
_values.Add("wrap_scope", My.Settings.Scope)
Dim _responseBytes() As Byte = _client.UploadValues("WRAPv0.9/", "POST", _values)
Dim _response As String = System.Text.Encoding.UTF8.GetString(_responseBytes)
At this point, we now have our
token, but it's part of a much larger string. We have to unpack the
token by splitting apart the name/value pairs with the name wrap_access_token=, and taking the value:
Dim _pairs() As String = _response.Split("&"c)
Dim _tokenPair As String = From p In _pairs Select p Where p.Contains("wrap_access_token=")
Dim _token As String = _tokenPair.Split("="c)(1)
Now we're ready to make
requests from our web service. This will require some changes to
previously written and tested code. If our client project doesn't have
references for System.ServiceModel and System.ServiceModel.Web, these need to be added. We also need to add an Imports System.ServiceModel to our form.
As we need to modify the
request headers to include the token, we'll need to create a request
proxy, add the token (remember we need to UrlDecode the token) as the
"authorization" header, and then call the service.
Private Function LoadFormStartupData(ByVal _token As String) As DataSet
Dim _startup As DataSet
Dim _binding As New WebHttpBinding(WebHttpSecurityMode.None)
Dim _address As New Uri(My.Settings.Scope)
Dim _channelFactory As New WebChannelFactory(Of ERPServiceReference.IERPServiceChannel)(_binding, _address)
Dim _proxy As ERPServiceReference.IERPServiceChannel = _channelFactory.CreateChannel()
Using TempOperationContextScope As OperationContextScope = New OperationContextScope(TryCast(_proxy, IContextChannel))
Dim authHeaderValue As String = String.Format("WRAP access_token=""{0}""", System.Web.HttpUtility.UrlDecode(_token))
WebOperationContext.Current.OutgoingRequest.Headers.Add("authorization", authHeaderValue)
_startup = _proxy.LoadStartupData
End Using
CType(_proxy, IClientChannel).Close()
_channelFactory.Close()
Return _startup
End Function
Using Access Control in a web service
We're now set to add Access
Control to our web service. We have a couple of configuration values we
need, and a config file is the ideal place, in case we need to edit
these values later. The usual place is in a web.config file, but recall that the web.config files are deployed as part of the compiled binary on Azure. Instead, we need to use the csconfig
file. The two settings we need to add are the Token Policy ID and the
Service Namespace. We use these as our known values when we compare the
tokens presented by the client application.
To add our settings to the WCF Role's csconfig file, right-click the JupiterMotorsWCFRole, listed under the Roles folder, and select Properties.
On the Settings tab, add our two settings.
Before we accept any
tokens presented to us, we need to validate them. We can write our own
token validation, but the AppFabric SDK includes a couple of classes we
can use as a starting point. The WCFAuthorizationManager project (found in<%install_path%>\Access Control\Exploring Features) contains an ACSAuthorizationManager class and a TokenValidator class we can use. ACSAuthorizationManager is an implementation of ServiceAuthorizationManager, and performs the following verifications of a request:
It checks whether there is an authorization entry in the request headers. If not, the request is rejected.
If there is an authorization token in the request headers, call the TokenValidator.Validate method. If the token is not valid, the request is rejected.
Claims are
extracted from the token, including a claim of type "action". We then
determine what action the user is attempting to perform (or what method
is being called). If there is no action, the request is rejected.
If all checks out, the action is allowed.
The TokenValidator class performs the following verifications of the token:
Confirms the HMAC signature is valid
Confirms the token has not expired
Confirms the issuer is trusted
Confirms the audience is trusted
We should add these two files to the JupiterMotorsWCFRole project. We may need to add a missing reference to System.ServiceModel.Web in order for the project to compile.