Using Windows Live ID with claims based authentication in ACS

Introduction

So far we have seen how we can realize claims based authentication with REST and ACS using a Facebook token. The reason that I began this blog with a Facebook token is that because of all identity providers in ACS (Google, Facebook, Windows Live ID and Yahoo!), Facebook is the one that exposes the most about a user. I.e.: The username and e-mail address are present in the token (among some other information). The username and e-mail address are also present in Google and Yahoo! tokens. With Windows Live ID however, only the identity provider (Windows Live ID) and a key that defines the user for a particular application is present. We are still interested in the username and e-mail address, because it defines our authorization scheme. In this blog post, I will explain how the username and e-mail address can be added and will expand the solution so far to include support for multiple identity providers.

Prerequisites

The following components are needed in order to complete this walkthrough with a working end to end solution:

  1. Visual Studio 2012
  2. .NET Framework 4.0 and 4.5
  3. The latest Windows Azure tools (In this walkthrough, June 2012 SP1 v1.7.50716.1601 is used)
  4. A Windows Azure account
  5. Windows Identity foundation (For Windows 8 users: you can activate this as a Windows feature in the control pane. Other users can download it here)
  6. The DPE.OAuth project, which is found in the source code attached to this walkthrough

The components that were created in the first blog post.
The source code can be found at the bottom of this post.

Analysis

Before the steps are given, an analysis is done to understand the problem and to make sure that different options are considered.
As mentioned before a Windows Live ID token only stores the identity provider and a key that is unique for a user and an application. We need to enrich this information with a username and password. Two options are available:

  1. Create a table in a database for our application that contains the key and add username and email columns to this table.
  2. Use Live Connect to obtain the username and e-mail address.

The first option has the following advantages:

  • Relatively quick and easy to implement.
  • Username and e-mail address is not shared across domains (our application and Windows Live).

It has also a disadvantage:

  • The username and password need to be registered manually.

The second option has the following advantages:

  • Information about a user is kept within Windows Live.
  • No data source is needed in the application.

It has the following disadvantages:

  • A custom login panel has to be implemented in order to leverage the functionality of using Live Connect.
  • For each claim, the user is presented with a warning to share data between the application and Windows Live ID.

In this walkthrough, we will select option 1.
For more information about the pros and cons of Windows Live ID, check http://msdn.microsoft.com/en-us/library/hh446535.aspx.

Steps

The following steps are needed:

  1. Create a database and a table in which to store the user’s key, username and e-mail (in this walkthrough we will create a SQL Azure database).
  2. Fill the table with user data.
  3. Create an edmx model in the webservice based on this database.
  4. Modify the webservice code to support a Windows Live ID user.

Note: In this walktrough, we manually add a user by its key to the database for simplicity. In an enterprise scenario we would rather create a page that checks if the authenticated user has been registered before in the database. If not, we present the user with a form to enter his/her username and e-mail address after which these values are stored in the database.

Create database

Creating the database in SQL Azure can be done as follows:
Logon to http://manage.windows.azure.com and go to “SQL DATABASES”.
Click on “ADD” in the bottom panel.
Specify a name, choose a new SQL database server and click on the arrow symbol in the lower right corner.

Specifying database settings in the Windows Azure Management Portal.

Specify a login name, password, the region and click on the check mark in the lower right corner.

Windows Azure SQL Database settings.

Windows Azure is now creating the database. This can take some time and the progress is shown in the lower bar.
When this has been completed, highlight the database and click on “MANAGE” in the lower bar.
A new window will be opened in which a username and password is requested. Fill in the values that have been specified before during the database creation and click on “Log on”.

Click on “Design in the lower left corner”.
In the center panel, click on “New table”.
Specify the name of the table and add columns corresponding to the user key, username and e-mail address. As can be seen in the following screenshot, I also added an identifier column.

Editing a table in the database management portal.

Click on “Save” in the upper panel.

Fill the table with user data

Click on “New Query” in the upper panel and specify a query to add a new user to the table that has just been created.
Below is an example. Note that the TokenNameIdentifier can be obtained from the identity provider when a user logs in with his/her windows live ID, using the client.

insert into dbo.WindowsLiveIdUser (ID, TokenNameIdentifier, UserName, Email) values (newid(), '[Windows Live ID token claim nameidentifier]', '[UserName]', '[Email]')

Substitute the [UserName] and [Email] in the query with a valid username and e-mail address.

The nameidentifier can be found when running the client application from the first post as is shown below:

Create edmx model in Visual Studio 2012

Open the webservice project from the first post.

Add a new item to the web role project and select the “ADO.NET Entity Data Model”.
Name it to as you see fit and click on “Add”.
Click on “Next” and click on “New Connection…”.

Go back to the windows azure management portal and click on your database.

Database name in the management portal.

Click on “Show connection strings” in the right panel and copy the value of the database server (it ends with “.database.windows.net”).
Go back to Visual Studio and paste the value in the “Server name” field.
Click on “Use SQL Server Authentication” and specify the same username and password that was used before in the database management portal of Windows Azure.
Select the database that you want to use (In this example MvcOdatADb) and click on “OK”.
Mark “Yes, include the sensitive data in the connection string.”, name the entity connection settings as you like and click on “Next”.
Select the WindowsLiveIdUser table, define the model namespace and click on “Finish”.

Select table in the entity data model wizard.

The model has now been added to the solution and the web.config contains an extra connectionstring entry.

Edmx model.

Modify the webservice code to support a Windows Live ID user

Currently we have a working solution for a Facebook user, which is our only supported type of user. Now that we are adding multiple types of users, we would like to take a modular approach. In this way we can add a Google and Yahoo! user without much effort, while our code is kept maintainable.
To realize this, let us take a look at the following diagram:

UML class diagram.

On the lower side all possible user types can be seen. For the moment we are only concerned about a Facebook user and a Windows Live ID user, but the other users are depicted here as well for completeness.
We need the username (Name()) and email address (Email()) of a user, regardless of its type. If the requirements would stop here, we could as well have defined an interface. Instead we have defined an abstract base class called UserBase in which the Name() and Email() methods are abstract.
Every type of user has a NameIdentifier and an IdentityProvider claim in its token. We don’t want to duplicate this method in every user class. The same holds for determining the first name and the last name of a user. The implementation of these methods is the same no matter the type of user. This is why they have been added and implemented in the UserBase class. This cannot be done with an interface.
Now that the approach has been justified, let’s take a look at how the implementation is realized.

First we defined an abstract class UserBase which contains the following implementation:

public abstract class UserBase
    {
        public abstract string Name();
        public abstract string Email();

        public string FirstName
        {
            get { return Name().Split(' ')[0]; }
        }

        public string LastName
        {
            get { return Name().Split(' ')[1]; }
        }

        public string NameIdentifier
        {
            get { return IdentityHelper.GetClaimValue(ClaimTypes.NameIdentifier); }
        }

        public string IdentityProvider
        {
            get { return IdentityHelper.GetClaimValue(AcsConstants.IdentityProviderClaimType); }
        }
    }

The last two methods use a helper method to retrieve the claim value.
The static IdentityHelper class has to be added as well and contains the following implementation:

public static class IdentityHelper
    {
public static string GetClaimValue(string claimType)
        {
            var identity = HttpContext.Current.User.Identity as IClaimsIdentity;

            if (identity != null)
            {
                //Since we append our IP after the ACS IP has been added to the token info, we choose to use LastOrDefault.
                var claimName = identity.Claims.LastOrDefault(c => c.ClaimType == claimType);

                if (claimName != null)
                {
                    return claimName.Value;
                }
            }
            throw new DataServiceException(401, "Unauthorized");

        }

        public static UserBase GetCurrentUser()
        {
            string identityProvider = GetClaimValue(AcsConstants.IdentityProviderClaimType);

            switch (identityProvider)
            {
                case AcsConstants.IdentityProviderFacebookRealm:
                    return new FacebookUser();
                case AcsConstants.IdentityProviderGoogleRealm:
                    return null;
                case AcsConstants.IdentityProviderWindowsLiveIdRealm:
                    return new WindowsLiveIdUser();
                case AcsConstants.IdentityProviderYahooRealm:
                    return null;
            }
            return null;
        }
    }

It contains two methods. The first method returns the value of a specific claim type. For example, if the claim type is NameIdentifier, it will return the value (in this example rlDeF… shown earlier in the client application).
Because the token contains two NameIdentifier claims (one from ACS Service Identity and the other from the SWT token that we generated), we will need to get the last claim. This is, because we have merged the SAML token information (in which the nameidentifier that is needed resides) from the Identity provider with the SWT token information from the Service Identity. Since we add the SAML information after the SWT token information we need to do a LastOrDefault.

The second method returns the type of user, depending on the identity provider claim.
The static AcsConstants class is shown next:

public static class AcsConstants
    {
        public const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";

        public const string IdentityProviderFacebookRealm = "Facebook-414626051932761";
        public const string IdentityProviderGoogleRealm = "Google";
        public const string IdentityProviderWindowsLiveIdRealm = "uri:WindowsLiveID";
        public const string IdentityProviderYahooRealm = "Yahoo!";
    }

The first value defines the claim type. Because there exists no ClaimType.IdentityProvider, we have to specify this in a constant.
The other constants refer to the values of the realms of each identity provider in ACS (see the “Realm” field in the properties of an identity provider in ACS).

Next, the WindowsLiveIdUser class has to be added and contains the following code:

public class WindowsLiveIdUser : UserBase
    {
        public override string Name()
        {
            using(var context = new WindowsLiveIdUserEntities())
            {
                var query = from user in context.WindowsLiveIdUser
                            where user.TokenNameIdentifier == NameIdentifier
                            select user.UserName;

                return query.FirstOrDefault();
            }
        }

        public override string Email()
        {
            using (var context = new WindowsLiveIdUserEntities())
            {
                var query = from user in context.WindowsLiveIdUser
                            where user.TokenNameIdentifier == NameIdentifier
                            select user.Email;

                return query.FirstOrDefault();
            }
        }
    }

As can be seen, the username and email are retrieved from the SQL Azure database that has been added previously using entity framework.

Finally, the authorization in PersonODataService.svc.cs has to be applied.

[QueryInterceptor("Person")]
        public Expression<Func> OnQueryPersons()
        {
            UserBase currentUser = IdentityHelper.GetCurrentUser();

            //Riccardo Corradin is the Administrator
            if (currentUser.FirstName == "Riccardo" && currentUser.LastName == "Corradin" && currentUser.Email() == "blueseaguy@hotmail.com")
            {
                return person => true;
            }

            return person => person.FirstName == currentUser.FirstName && person.LastName == currentUser.LastName;
        }

As can be seen, authorization has been added and without knowing the explicit type of the user, the abstract class is used to verify the first and last name as well as the email address.

Sourcecode

Conclusion

Adding information to a Windows Live ID token, requires to create a custom database and table, in which this information is stored. The strength of this approach, is that information about usernames and e-mail addresses are kept within the application and that the implementation is fairly straightforward.

Any thoughts/suggestions about this blog post are greatly appreciated!

2 thoughts on “Using Windows Live ID with claims based authentication in ACS

  1. The implementation of these methods is the same no matter the type of user. This is why they have been added and implemented in the UserBase class. This cannot be done with an interface.

    ==> This is not entirely true you can implement an interface on your base class.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace ConsoleApplication1
    {
    public interface IPersonInterface
    {
    string name { get; set; }
    string email { get; set; }
    }

    public abstract class Person : IPersonInterface
    {
    public abstract string name {get; set;}
    public abstract string email {get; set;}
    }

    public class Male : Person
    {
    public override string name { get; set; }
    public override string email { get; set; }

    public Male()
    {
    name = “MyName”;
    email = “Me@Hotmail.com”;
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    var me = new Male();
    Console.WriteLine(me.name + ” ” + me.email);
    Console.ReadLine();
    }
    }
    }

    1. Hi me,

      Yes you are correct. What I intended is that if you want 1 class to contain both:
      1. Methods that must be implemented in the classes that are inheriting from the 1 class.
      2. Methods that contain an implementation.

      In this case you need an abstract base class to fulfill these two requirements.
      Nevertheless, you have a most valid suggestion to enhance the code maintainability: create an interface above the abstract class.

      Thank you for sharing!

Leave a Reply