More on Locale in Windows Azure

A few days ago I published a short post on controlling the locale for Windows Azure Applications, turns out that a significant piece was missing – whilst all that was written was well and true for web applications, the story for WCF services is slightly different –

By default web services do not run in ASP.net compatibility mode, and without this, many system.web settings in the web.config do not take effect, the MSDN article Hosting and Consuming WCF Services contains the following paragraph (bold is mine) –

ASP.NET Compatibility Model

When hosting your WCF services in a load-balanced or even a Web-garden environment where subsequent requests in a session can be processed by different hosts or processes in the environment, you need out-of-process persistent storage for your session state. Out-of-the box WCF doesn’t support persistent storage for session state. Instead, WCF stores all its session state in memory. When your WCF services are hosted in IIS, you can end up with recycling scenarios, as described in the previous section. Instead of building persistent storage for sessions all over again, WCF relies on the ASP.NET implementation for session state. This approach has one serious limitation: you limit your services to HTTP.

ASP.NET session state is not the only feature that is supported by the ASP.NET compatibility mode. It also supports features such as the HttpContext, globalization, and impersonation, just like you are used to with ASP.NET Web services (ASMX). Refer to MSDN Help for the ASP.NET–specific features to enable out-of-process session state.

And so – if you wanted to use the <globalization> element to control the locale of WCF service you must ensure your services are running in ASP.net compatibility mode, as shown in the MSDN article, this can be done by adding the following attribute to the service implementation –

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class Service1 : IService1

But to allow that you would also need to add the following entry to the web config –

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>

If you’d rather not run in compatibility mode, an alternative is to set the thread’s UI culture, as shown in my previous post, in the service’s constructor.

Note: for completeness I should add that this applies to WebServiceHost and REST services as much as it does for ServiceHost and SOAP based services, I have tested both.

Advertisements

End-to-end authentication and authorisation scenario for MVC+ACS

Background

Windows Azure’s Access Control Service (ACS) enables developers of web application and services to provide a seamless single-sign-on experience for their users, easily and quickly, building on standard protocols such as OAuth, WS-Federation and SAML.

ACS’ built in support for Live, Google, Yahoo and Facebook Identities as well as the easy integration with ADFS and AD means that authentication with the most used identities is literally done with a few clicks and a little bit of configuration.

For an overview of the ACS service and a useful how-to tutorial see MSDN on – http://msdn.microsoft.com/en-us/library/gg429781.aspx

Using ACS with well-known identity providers, other than custom authentication solutions, as part of the application provides several benefits –

From the users’ perspective it prevents the need to remember a different set of credentials for the application, instead using existing identities to sign-in; this also increases security as users tend to use the same credentials for many applications, not all are good at protecting this information.

From the application’s perspective it removes some of the effort required in building scenarios such as managing credentials – storing them securely, implementing authentication functionality as well as capabilities such as reminding/resetting passwords, etc.

However – whilst integrating an application with an identity provider (or several) provides two (generally trust-worthy) facts – the knowledge that the user has been authenticated by the approved identity provider(s) and a unique identifier for that user – it does not, on its own, provide a complete end-to-end solution for authentication and authorisation; several pieces are needed on top of the ACS and IP integration beyond uniquely identifying a user, such as managing the user’s profile and implementing role based authorisation.

In this post I will be looking at the steps that are required to provide an end-to-end story for an ASP.net / MVC application using ACS with multiple identity providers to drive authentication and authorisation scenarios, I’ll start by discussing managing users’ profiles –

Managing Users’ Profiles

The Identity Providers, through ACS, will provide the application a unique identifier for the user –

The initial request from the user’s browser to the application will come as unauthenticated; at this point, given the right configuration,  Windows Identity Foundation will redirect the request to ACS which will, in turn, interact with the identity provider (as needed) before redirecting back to the application, this time with a bunch of claims regarding the details of the identity provider and the user’s identity provided through the IClaimsPrincipal object.

It does not, at this point, give you much information about the user – some identity providers, such as Google, might provide the user’s first and last name and perhaps an email address, others, such as Live ID, will only provide a unique identifier – nor does it tell you whether the user is allowed to access your application. All you know is that this is user x as declared by identity provider y.

This might be good enough for web sites that do not restrict access, and only need to know a unique id for a user, for example for personalisation purposes or to store data for a particular user, http://www.stackoverflow.com is an example for such site.

Most web sites, however, would like to, at the very least, know some basic information about the user, such as full name, perhaps date of birth or email address; some – of course – require a much more elaborate profile.

Some web sites will let anybody in, but will require updating the profile, others are membership only and so – knowing the identity of the user is one half of the story – matching it against a membership database being the other.

To support either of these scenarios, the application will need to have its own store of users’ information, linked to the identity provided through ACS.

As requests arrive from the ACS the application will need to be able to refer to this store to identify whether the user is known or new. Known users will be let in (subject to authorisation, discussed later in this article); unknown users will be, for example, directed to a registration page.

image

This really isn’t much different from how this would be implemented without the ACS- if identity was provided by ASP.net membership, for example – the main difference is that when implementing single-sign-on the identity piece and the profile management/authorisation piece are separated.

On Windows Azure, table storage is a great option for storing user’s profile – records could be stored, for example, against the identity provider (as the partition key) and user’s identity (as the row key), and given that this will generally be the only access mechanism required (I’ll be discussing a variation of that – for supporting multiple identities for the same user), it keeps the solution nice and simple.

From a technical point of view – the application needs to first pick up the user’s identity, as provided by ACS, and check that against the user’s store, and it needs to do that before running the application’s code so that the user can be considered when evaluation authorisation.

One way of achieving this is leveraging the Windows Identity Framework pipeline by implementing a custom authorisation manager by inheriting from ClaimsAuthenticationManager 

By overriding the Authenticate method you can get access to the identity provided by ACS, interrogate the claims provided with it and even make changes to the claim-set as needed.

The first step in the authenticate method would be to extract the principal as an IClaimsPrincipal –

IClaimsIdentity identity = (IClaimsIdentity)incomingPrincipal.Identity;

The next step would be to ensure that the user has actually been authenticated as this method will get called twice – once for the initial unauthenticated request, before the redirection to ACS, and once when the user is redirected back to the application from ACS with the authentication token; We’re only interested in the second call and so if the user is not authenticated we do nothing. The module’s default behaviour will take care of redirecting unauthenticated users if this was the WIF configuration.

if(identity.IsAuthenticated)
{       //code goes here
}

For authenticated users, we need to extract the claims we’re expecting from the token and ensure they exist – these are the nameidentifier and identityprovider claims

Claim id = identity.Claims.FirstOrDefault(claim => claim.ClaimType ==
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"); Claim provider = identity.Claims.FirstOrDefault(claim => claim.ClaimType ==
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"); if (id == null || provider == null) throw new ApplicationException(
"Reuqest did not contain the necessary authenticaiton information");

It might be obvious, but to avoid any doubt it is important to note that the user’s identity has to be composed of these two – the identity is unique in the context of the identity provider, theoretically two provides might use the same identity.

So – now that we have the user’s unique identity we can check whether it exist in our users store, this is straight forward coding against Table storage, so there’s no point repeating all the details, I have it encapsulated in two method calls

UserLineDataServiceContext context = UserLineDataServiceContext.GetContext();
UserLine user = context.FindUser(provider.Value, id.Value);

At this point user either hold the details of the user found or is null if the user has never been registered; if the user has been registered before I populate a bunch of claims specific to my application –

if (user != null) { identity.Claims.Add(new Claim(ClaimTypes.Role, "RegisteredUser")); Claim nameClaim = identity.Claims.FirstOrDefault(
                                 c => c.ClaimType == ClaimTypes.Name); if (nameClaim != null) identity.Claims.Remove(nameClaim);

      identity.Claims.Add(new Claim(ClaimTypes.Name,
                                user.FirstName + " " + user.LastName)); identity.Claims.Add(new Claim(ClaimTypes.GivenName, user.FirstName)); identity.Claims.Add(new Claim(ClaimTypes.Surname, user.LastName)); }

The first claim I populate is a role claim of a ‘RegisteredUser’, I will be using this in my application to ensure that only users with this role can access pages other than the register page as part of my authorisation implementation.

I then populate the name-related claims; this allows me to present the user’s name as given to my application in the sign-out control and other areas of my application.

Note: some IPs (such as Google) will provide you with the user’s name, others might not, in either case I allow my user to override the name with the one she wishes to use in my application, and so for registered users I need to override any claims provided by the IP.

At the end of the Authenticate method I call the base method to ensure any standard behaviour of WIF is executed –

return base.Authenticate(resourceName, incomingPrincipal);

And so – by using a few lines of code in a custom ClaimsAuthenticationManager and utilising Windows Azure Table, we’ve enabled the application to manage it’s users, distinguishing between registered and unregistered users.

The next  step would be to implement authorisation and allow unregistered users to become registered –

Role Based Authorisation and the registration page

You would have noticed the custom Claims Authorisation Manager added, for known users, the RegisterUser claim – a claim of type ‘ClaimType.Role’ – by default WIF translates claims of this type to ASP.net roles allowing familiar role based authorisation techniques to be used, in my example I’ve used this to control access to the rest of the application and to direct unknown users to the registration page.

In my case I’ve decided that the application can be accessed by any user, but it requires that users register with it application directly.

To this behaviour, preventing unregistered users access to the majority of the application, I’ve added the authorise attribute, requiring the ‘RegisteredUser’ role for access, on all my controllers other than the default controller –

[Authorize(Roles="RegisteredUser")]

By doing so, and given that this role will only be available for users that were found in the applications user’s repository, I ensure that unknown users, even if authenticated by the identity provides supported, will not be able to access any part of the application other than the home controller (which does not have this attribute)

Note: To bulletproof this approach the ClaimsAuthenticationManager should check that incoming requests do not contain the Role Claim with the text ‘RegisteredUser’ 

On the home controller I have two actions, Index and About, both are available for any user, the Index action has the following code –

public ActionResult Index()
        {
            if (!User.IsInRole("RegisteredUser"))
                return RedirectToAction("Register", "Account");
            else
            {
                return View();
            }
        }

As you can see – as users land in the default action for the application, if they are not registered they are redirected to the Register action of the Account Controller – an action that is available to any users, this action will display the registration form asking the user for details such as name and date of birth, the post action for this form looks as follows –

UserLine user = new UserLine(encodeKey(collection["IdentityProvider"]), encodeKey(collection["Identity"]));
            user.FirstName = collection["FirstName"];
            user.LastName = collection["LastName"];
            user.EmailAddress = collection["EmailAddress"];

            UserLineDataServiceContext context = UserLineDataServiceContext.GetContext();
            context.AddUser(user);
            ClaimsIdentity identity = User.Identity as ClaimsIdentity;
            
            identity.Claims.Add(new Claim(ClaimTypes.Role,"RegisteredUser"));
            //add name claims according to registration information
            identity.Claims.Add(new Claim(ClaimTypes.Name, user.FirstName + " " + user.LastName));
            identity.Claims.Add(new Claim(ClaimTypes.GivenName, user.FirstName));
            identity.Claims.Add(new Claim(ClaimTypes.Surname, user.LastName));

            return RedirectToAction("Index", "Home");

Admittedly not the most robust code in the world, but good enough as a sample it creates a new UserLine, populating it with the information from the form and adds it to the Table before adding all the necessary claims for this user.

These claims, including the RegistredUser role claim would normally be added by the ClaimsAuthenticationManager but in this case they are now added in this form to allow the user to be treated as a recognised user by the application.

With these set the user can be redirected back to the default action, this time with the correct role which would allow the default view to be returned.

Supporting Multiple Identities for same user

Everything that discussed so far assumes the use is only identified using one identity provider and whilst this is a fair assumption for some web sites, most of those who wish to support identity federation want to support more than one provider and to make user’s life as convenient as possible it is important to be able to recognise users using more than one identity provider.

Given that there’s no way for any one IP. or the ACS, to link identities, this is up for the application, or – more accurately- up for the application to allow the user to do so.

I haven’t fully implemented this for my sample, but the approach would be to allow the user, in the registration page, to indicate that she is already known using a different identity and then to be able to provide a token for this identity (through ACS, of course).

The key to this is to expand the user’s repository – every user should be given an ‘internal identity’ – managed by the application and separate table will link any IP-provided identity to the relevant internal identity, any records in the application should always be stored against the internal identity.

Summary

I hope that through this post I was able to demonstrate that whilst enabling ACS for an application is only a first step towards achieving a full end-to-end authentication and authorisation solution for an application, the steps required to complete the solutions are quite straight forward and lean, and that’s the whole point in identity federation – to take away the majority of the work needed, whilst leaving a good level of control in the application.

Locale on Windows Azure

Two of the benefits of using the Windows Azure platform is the ability to deploy applications globally and avoiding the need to manage the hardware as well the O/S; however – like everything else in life – this comes at some ‘price’, and one element is control over the environment.

In Windows Azure all instances are created with the en-US locale by default and if your application is deployed outside the US, and you’re not handling this properly, this may cause some confusion.

image

To demonstrate this I’ve create a simple application using the ASP.net template and added a textbox, a button and  a label

In the Page_Load I’ve updated the label with DateTime.Now.ToString() and when I run my application on my UK laptop I get the expected result –

image

However, deploying and testing this on Windows Azure the result is different – the date shown is in US format (MM/dd/yyyy) rather then the UK format (dd/MM/yyyy) –

image

The same issue exists when trying to parse a date – entering the date 30/1/2012 into the textbox and clicking on the button which includes the logic –

Label2.Text = DateTime.Now.ToString("MM/dd/yyyy");

result with the correct date displayed in the label when running locally, but an exception when running in Azure (as there’s no month 30, of course)

So – what can one do?

Well – theoretically one can change the locale on the machine, either by using remote desktop (hardly a scalable and reliable approach) or, better yet, by employing a startup task to do this, but this has the potential of confusing the fabric controller and generally speaking – one should not meddle with the O/S unnecessarily.

So – this should be handled at the application level rather than the system level, what’s are the options?

Well – in my simple scenario I could have simply provided the required format in my code – if I had my Page_Load logic as DateTime.Now.ToString(“dd/MM/yyyy”)  I would have avoided the different behaviour between environments, similarly I could have provided the format when parsing the date

IFormatProvider cultureInfo = new CultureInfo("en-GB",false);
Label2.Text = DateTime.Parse(TextBox2.Text,cultureInfo).ToString();

But this could be quite cumbersome for a real application.

Another option is to set the Culture on the thread of the application –

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB");

but I would need to do this on every page load, so that might be a bit cumbersome as well (but that’s a good option when you need to set the culture based on the user request, for example)

For a blanket rule option, seems like the web config is the best option – simply add

   <globalization culture="en-GB"
       uiCulture="en-GB"
    /> 

to the system.web section of the web config and this culture will be applied to all requests.

A good summary of the options can be found here

Note: turns out there’s a bit more to the story, read my follow up post

%d bloggers like this: