I wanted to find out what the options were for building access control into an application hosted in the cloud, so that I could limit what was made available to different users of my Azure hosted application. More precisely I wanted to be able to use someone’s LiveID as the authentication mechanism, and use that to control which areas of the site that person could see or access.
Option 1 — Blob Storage in Azure
So what options are there in in Azure itself? Looking through the Azure SDK documentation there is a discussion of access control for Azure Storage Services, so I thought this might provide the basis of an access control mechanism. On closer inspection I realized that this wasn’t going to help. First of all access control is only available for Blob Storage, and not for Queue or Table Storage. Secondly, a blob can only be made public or private, with no finer control over who can see the blob’s contents.
If you do want to use the Azure Storage Services you do (with the exception of public blobs) need to authenticate your request using your Azure account credentials. Given that Azure Storage is accessed using a REST API this means providing your encrypted credentials as part of the HTTP request. There is some help provided with this in the Azure SDK samples: take a look at the StorageClient Sample. The sample includes a client library that wraps the REST API, and also shows how you can access the .NET Client Library for ADO.NET Data Services to access Azure Table Storage.
This example shows the type of code you would write to use a private blob, notice that the access control is set on the container, rather than the blob itself, and the useful utility method that pulls the credential information from your config file.
BlobStorage bs = BlobStorage.Create(StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration());
BlobContainer bc = bs.GetBlobContainer("list");
bc.CreateContainer(null, ContainerAccessControl.Private);
This code shows how you can then write to the blob using the sample client library, which is hiding all of the REST API from you:
BlobProperties props = new BlobProperties(txtItem.Text);
props.ContentType = "text/plain; charset=UTF-8";
BlobContents content = new BlobContents(Encoding.UTF8.GetBytes("This is the data to be stored in the blob!"));
bc.CreateBlob(props, content, true);
Option 2 — Membership Provider in Azure
The second option I looked at in Azure was to use ASP.NET membership. The default membership providers in ASP.NET use either SQL Server or Active Directory as their data sources, so neither are likely to work for a cloud hosted application. Digging around in the Azure SDK samples revealed this project here: AspProviders Sample. This project includes ASP.NET providers for Membership, Role, Profile and Session State. (Note: there is no ‘out of the box’ Session object in an Azure application because of the deployment model, this provider stores session state in Azure Storage.) The Membership and Role providers do exactly what you’d expect, and work with the standard ASP.NET Login controls. The providers use Azure table and blob storage to persist their data. This makes it very straightforward to set up authentication and authorization for your cloud application. What you would have to spend some effort on would be creating some administration tools to manage users and access control rules for the site.
Option 3 — Using Live ID with the Azure Membership Provider
My goal though was to use Live ID as my authentication mechanism, so is there a way of integrating Live ID with the Azure membership provider? My starting point here was to download the Windows Live Tools for Microsoft Visual Studio. The tools include a number of controls, in particular IDLoginStatus and IDLoginView, and a couple of new Visual Studio project templates: Windows Live Web Application and Windows Live Web Role (template for Windows Azure Cloud Projects) that looked useful. These two controls simplify using a Live ID to authenticate with your site, handling the redirects to the standard Live login page and back to your site. Although the controls a simple to use, there is a bit of setup to manage first. To start with you need to identify your site to Live and this requires a unique Application ID, a Secret Key and a return URL so that Live can redirect the browser back to your site after a successful log in. This can all be done at Live Services, where you can create a new project to define this information. You can then add an IDLoginStatus control to your page, and then chose the ‘Configure Application ID’ task to automatically add the Application ID and Secret Key to your web.config file.
The IDLoginStatus control has a ‘LoggedInLiveID’ property that tells you whether the user has logged in or not. What you can’t do is find out anything more about the user, other than their unique ApplicationUserID.
Is there a way to associate the ApplicationUserID with a user in the membership system, and so implement some access control on your site? This is where the second control, IDLoginView, comes in. This is a templated control with these templates:
- AnonymousTemplate: the user is not logged in at all.
- LoggedInTemplate: the user is only logged in using the ASP.NET membership system.
- LoggedInIDTemplate: the user is only logged in with a Live ID.
- LoggedInAllTemplate: the user is logged in with both the ASP.NET membership system and with a Live ID.
- AssociatePromptTemplate: the user is prompted to associate their Live ID with their membership id.
The control can also be configured to automatically associate their Live ID with their membership id, and once the association has been made then then logging in with Live automatically logs the user into the membership system. So from then on I have a site that uses a Live ID for authentication, and ASP.NET membership to control access.
I got all of this working in a standard ASP.NET application, and then tried it in a cloud application and this is where I got stuck. The IDLoginView control sets up the association between the membership ID and the Live ID by adding a new table ‘aspnet_LiveIDAssociation’ to the ASPNETDB database, and assumes that the membership provider is the standard SQL membership provider. Running in the cloud, I’m not using the standard membership provider, so any attempt to add an association fails. To get this scenario to work I’d probably have to modify the sample Azure membership provider and handle a lot of the Live ID login process manually…
Option 4 — Using Live ID and the Access Control Service
Access Control Service is one of the cloud infrastructure services (along with the Service Bus and Workflow Service) that are part of the Azure platform. The Access Control Service is ‘a hosted, secure, standards-based infrastructure for multiparty, federated authentication and rules-driven, claims-based authorization’. I wanted to see if I could use it as an access control mechanism for a web site where I was authenticating users with Live Id.
To adopt some of the correct terminology, the Access Control Service is a Security Token Service (STS) and my web application is a relying party (RP). To briefly summarize how this process will work:
- My site prompts the user to login to the Access Control Service with their Live ID.
- The Live ID is mapped by the Access Control Service to set of claims (permissions) defined by me.
- The request is redirected back to the original page on my site. Elsewhere on my site the claims can be examined, and the appropriate logic applied to determine what the user can see or do.
There are a couple of important points to note at this point:
- This process is complicated. Trust relationships must exist between the various parties; the Access Control Service is pre-configured to trust the Live Id system, but I have to define the trust relationship between my application and the Access Control Service. Information transferred between the sites must be secure.
- The system is standards based, meaning that other authentication and authorization systems could be plugged in.
- My application has completely outsourced the authentication and authorization rules. All my application needs to do is examine an incoming claim, such as ‘this person is an administrator’, and decide on the basis of that claim whether the user can see that page or perform that action.
There is no configuration needed for the Live ID login because there is already a trust relationship setup for Access Control Service, but I did have to code the login rather than use the IDLoginStaus control:
string homeRealm = "http://login.live.com";
string scope = "http://localhost/LiveACS/";
string acs = "https://accesscontrol.windows.net/passivests/YOURSOLUTIONNAME/LiveFederation.aspx";
var request = new SignInRequestMessage(new Uri(acs), scope);
request.Parameters.Add("whr", homeRealm);
Response.Redirect(request.RequestUrl);
The homeRealm is reason we can’t use the IDLoginStatus control, because the current version of the control doesn’t support the whr parameter.
The scope is used to match to a set of rules in your Access Control Service solution, and the solution name must be part of your Access Control Service address.
You can manage your Access Control Service solution here. You define a scope to hold a collection of rules, and a rule is made up of input claims (in this case a Live ID) and an output claim (in this case an Action), you also need to upload the public key of the certificate you will be using to encrypt the claim information that will be transferred to your application. (Note: if someone logs in but no input claims are matched here, then all they’ll see will be a standard 403 message).
Input Claim | ||
Type: Windows Live ID | Value: someone@liveid.com | Issuer: live.com |
Output Claim | ||
Type: Action | Value: LiveACS.ViewData | Issuer: accesscontrol.windows.net/YOURSOLUTIONNAME |
At this point I need to introduce another component. So far I’ve set up my site to enable logins using Live ID, the login can be used by the Access Control Service to identify a set of claims to be encrypted and transferred to my application. How can a decrypt and understand the claims in my application? I could use WCF and the System.IdentityModel.Claims namespace, but the recommended approach seems to be to use the new Geneva Framework and the Microsoft.IdentityModel.Claims namespace for this job instead.
Once the Geneva Framework SDK is downloaded I ran the FedUtil.exe utility to update my web.config file:
<microsoft.identityModel>
<audienceUris>
<add value="http://localhost/LiveACS/" />
</audienceUris>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry">
<trustedIssuers>
<add thumbprint="416e6fa5d982b096931fbf42c4a3dcd608856c95"
name="http://accesscontrol.windows.net/YOURSOLUTIONNAME/"/>
</trustedIssuers>
</issuerNameRegistry>
<federatedAuthentication enabled="true">
<wsFederation passiveRedirectEnabled="true" issuer="https://login.live-int.com/login.srf" realm="http://localhost/LiveACS/" />
</federatedAuthentication>
<applicationService>
<claimTypeRequired />
</applicationService>
<serviceCertificate>
<certificateReference x509FindType="FindBySubjectDistinguishedName" findValue="CN=localhost" storeLocation="LocalMachine" storeName="My" />
</serviceCertificate>
</microsoft.identityModel>
It added a bit more than what I’ve shown above – setting up various imports and modules – but these are the important elements. audienceUris determines where the tokens containing the claims will be sent. trustedIssuers specifies whose tokens we should trust. serviceCertificates identifies where to find the private key to decrypt the token that came from Access Control Service (this is the private key corresponding to the public key I uploaded to the scope).
Once all that is in place I can examine the claims in code and control access to parts of my site like this:
if (ClaimsVerification.ViewDataPermission) Label1.Visible = true;
Which uses a simple utility class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.IdentityModel.Claims;
using System.Threading;
public class ClaimsVerification
{
public static Boolean ViewDataPermission
{
get
{
IClaimsIdentity identity =
Thread.CurrentPrincipal.Identity as IClaimsIdentity;
if (identity == null) return false;
return identity.Claims
.Where(claim => claim.ClaimType.Equals("http://docs.oasis-open.org/wsfed/authorization/200706/claims/action") &&
claim.Value.Equals("LiveACS.ViewData"))
.Count() > 0;
}
}
}
The ClaimType here is the full name of the ‘action’ type output claim I defined in the Access Control Service scope.
Can I use all this in a cloud application as I wanted? Unfortunately not yet – the reason being that the Geneva Framework classes are not available as part of the cloud services at the moment, so I guess I’ll just have to be patient!
No comments:
Post a Comment