Azure Acs plus asp.net MVC memberships


Over the past week I have been trying to get up to speed with the concept of claims aware applications, the windows identity foundation framework and the Azure AppFabric access control service. I was asked to look into this as an option for allowing customers to login to our website with their existing windows live, Google, Yahoo and Facebook logins. I had the following requirements:

  • Minimal effort in terms of new code,classes or complexity added.
  • Minimal changes to existing login/authentication workflow (our site uses a custom asp.net membership provider but still uses iPrincipal).
  • The login page should have the login/password control and the links/buttons to login using Google/Yahoo/Windows Live/Facebook.
  • This should work on any of our website’s wildcard subdomains.

After Googling, reading MSDN and doing a couple walkthroughs I finally figured out a way to achieve most of my requirements. During my search I found quite a few people trying to find a way to add single signon services to their asp.net websites using ACS (Azure App Fabric Access Control Service) and other Secutirty Token Services (STS). Although it doesn’t fulfill all of my requirements, I figured someone might find what I came up with useful. I have not figured out how to fulfill the final requirement because ACS requires the replying party information before hand. There is no room for wildcard urls and therefore I have no idea how I  could allow a single site such as a dotnetnuke site or a website with wildcard subdomains to act as a relying party for all child sites. If anyone has any ideas let me know. I am thinking ACS OAuth2 but I have not looked into it yet.

If you have an existing website and simply want to add a “login with facebook” button on your login page, I doubt you are looking to  delegate your entire authentication and authorization system to ACS or any other external STS.  Unfortunately, the samples and documentation I came across (Very good BTW) cover the broader concept of federation and claims both as an option for removing authorization and authentication logic from your application and streamlining trust relationships between internal an external parties. The parties in the examples usually have most if not all the claims required for a user to interact with a system. Unfortunately, The default identity providers available in the ACS ( Facebook, Yahoo, Google, Windows Live) only offer enough information to identify a user by name, email or a unique id. Windows live only offers the latter. This means that you can’t use their limited claimset for anything more than an identifier (this is really all we need). My goal here is to allow users to associate their identities to their existing asp.net membership accounts by adding a few steps to the existing authentication workflow.

The following graphic is a simplified representation of the workflow.

In the workflow, the “website ACS signin page” is the url you tell ACS to send the user to after they have been successfully authenticated. This is the place where I check the identity claim against the database and sign in the appropriate user. By the time execution gets to the “website ACS signin page” the ClaimsPrincipal is set and the authentication module cookie is set so after I get the identity claims from the ClaimsPrincipal I delete the authentication module cookie and reset the principal to the associated user. If the user is not already associated, they are taken through the association process. It is also possible to add a new user signup to the association process.  I decided to store the association information in a table called UserIdentity which I  access with Entity framework using linq. This table has a row for each of the user’s identities (1 for google, 1 for yahoo, 1 for windows live etc).

Table Structure

Sample Data

Corresponding linq query on “website ACS signin page”.

//Extract claims
var identityClaim = new MyIdentityClaim(HttpContext.user as ClaimsPrincipal);

//Delete session cookie so the module cannot reset principal
SessionAuthenticationModule sam = FederatedAuthentication.SessionAuthenticationModule;
sam.DeleteSessionTokenCookie();

if(identityClaim.HasIdentity)
{
var db = new identityEntities();
var userid = (from ui in db.UserIdentity
               where ui.IdentityValue == identityCliam.IdentityValue && ui.IdentityProvider == identityClaim.IdentityProvider
               select ui.UserId
             ).FirstOrDefault();

   var user = Membership.GetUser(userid);
   if(user!=null)
   {
         FormsAuthentication.SetAuthCookie(user.UserName, false);
   }
   else
   {
       //Save identity values for processing in the association page
       identityClaim.SaveToSession();

      //Send user into the association process
      return RedirectToAction("Associate", "Account");
   }

}

EDIT:
Here is the code for MyIdentityClaim


    public class MyIdentityClaim
    {
        private string _identityProvider;
        private string _identityValue ;
        public const string ACSProviderClaim = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";

        public MyIdentityClaim(IClaimsIdentity identity)
        {
           
            if (identity != null)
            {
                foreach (var claim in identity.Claims)
                {
                    if (claim.ClaimType == ClaimTypes.NameIdentifier)
                    {
                        _identityValue = claim.Value;
                    }
                    if (claim.ClaimType == ACSProviderClaim)
                    {
                        _identityProvider = claim.Value;
                    }

                }
            }


        }

        public bool HasIdentity
        {
            get
            {
                return (!string.IsNullOrEmpty(_identityProvider) && (!string.IsNullOrEmpty(_identityValue)));
            }
        }

        public string IdentityProvider
        {
            get
            {
                return _identityProvider;
            }
        }

        public string IdentityValue
        {
            get
            {
                return _identityValue;
            }
        }


    }

Now you can  add your “login with facebook” button on your Aspnet member ship website with minimal changes to your existing code. Here is my modified signin page.

I used the MVC3CutomSigninPage sample found here as a starting point for my proof of concept. This sample also shows you how to get the list of available providers from ACS to create the blue buttons seen above. I don’t have time to create a full walkthrough but I believe the information I provided should help people get over that hump. I will be happy to answer questions.

EDIT:
Download the acs samples here. The MVC3CutomSigninPage sample can be found in the ‘Websites’ folder. The online readme file for this sample can be found here. You can add the code I provided after getting the sample to work.

After you have confirmed acs is working you can change the web.config back to forms authentication:

  <authentication mode="Forms"><forms loginUrl="~/Account/LogOn" timeout="2880" /></authentication>

Also make sure passive redirect is disabled:

<wsFederation passiveRedirectEnabled="false" issuer="https://[namespace].accesscontrol.windows.net/v2/wsfederation" realm="https://yoursite.com/" requireHttps="true" />
About these ads

16 Responses to Azure Acs plus asp.net MVC memberships

  1. TravelingMan says:

    This is very interesting, I am currently in the process of creating the same setup for my own website. Thanks for the post.

    • Garvin says:

      You are welcome

  2. Garvin says:

    The two WIF models apparently convert the principal to a claimsprincipal with a null username. This causes problems if you use Membership.GetUser() when users are not authenticated.

  3. For wildcard subdomains, have a look at this:

    http://social.msdn.microsoft.com/Forums/en-US/windowsazuresecurity/thread/f3e04c29-befc-4955-b66d-569d29277a57

    • Garvin says:

      Thanks for the link Bill. I read Steve Syfuhs’ response but the last time I checked, azure acs gives an error if your rp, realm and return url doesn’t match. This occurs before returning to the client where the cookie will be read.

  4. Andrew says:

    Hello! I’m working on some project and was stucked on a problem similar to this. Can you please email me sample solution you have described. Thank you a lot.

    • Garvin says:

      Hi, I don’t have a full sample but you can use the codeplex project sample I linked to in the article. I simply added the code you see above to that project to a method in the account controller. Is there something specific you are struggling with?

  5. Garvin says:

    For those who are wondering, the MyIdentityClaim class is something I created for the sole purpose of extracting and returning the user’s unique id and the provider from the claim set.

  6. foolioiglesias says:

    So in your web.config are you using ? Because when using ACS this is usually commented out.

  7. foolioiglesias says:

    In my comment above I meant to say using authentication mode “forms”

    • Garvin says:

      Yes, I am using forms authentication.

  8. Garvin says:

    I will talk more about adding the provider buttons to the login page in a subsequent post.

  9. Salvador says:

    I’m amazed, I have to admit. Seldom do I encounter a blog that’s equally educative and entertaining,
    and without a doubt, you have hit the nail on the head.
    The problem is something that not enough people are speaking intelligently about.

    I am very happy I came across this in my hunt for something concerning this.

  10. Hugh says:

    Gday admin, I just wanted to give you a quick heads up that your current URL:

    http://garvincasimir.wordpress.com/2011/08/06/azure-acs-plus-asp-net-mvc-memberships/

    is being flagged as a potentially malicious site in my internet browser firefox.

    I would highly suggest having someone look into it.
    You can easily lose a lot of visitors due to this kind of issue.
    Best of Luck.

    • Garvin says:

      What version of FireFox are you using? I did not get this error in mine. Is there a setting or plugin I should be looking for?

    • nikivancic says:

      I accessed this article with 4 different browsers (Firefox included) without seeing any indications about potentially malicious site.

      Just adding to statistics :-)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: