Monday, April 29, 2013

Claims-based authentication in a web application using ACS

I like the idea to use existing user accounts for authentication instead of managing users by myself.
Otherwise I have to store user names and passwords in my database. Therefore I have to provide a user interface to create, change or delete them. And most important, I have to ensure that everything is secure. Nothing I really want to bother when I am developing some cool application.
Also for the user does not like to create a new account for every web site she visits. This is quite boring, especially as she should use different passwords for each site. That arises the need to store somewhere the different passwords?
To cut a long story short: why not use existing user accounts like Google, Windows Live ID, Facebook, … ?

Integrating ACS

A good starter is How to: Create My First Claims-Aware ASP.NET Application Using ACS. Unfortunately, this article is a little bit outdated in between (written in April 2011). Mainly the toolset (Visual Studio 2012, Identity and Access Tool) changed.

Web Application

To demonstrate the principle, start with an ASP.NET Empty Web Application. Add a Web Form with some text in the body like
Hello <%= User.Identity.Name %>
When you start the debugger, you should see a web page with text Hello – but without user name, since no authentication is done.

Configure ACS

Before we can add authentication, we have to do some configuration. Just follow the first steps from the document mentioned above (How to: Create My First Claims-Aware ASP.NET Application Using ACS):
  • Step 1 - Create an Access Control Namespace
  • Step 2 – Launch the ACS Management Portal
  • Step 3 – Add Identity Providers

Identity and Access Tool

With Visual Studio 2012 the ACS integration is no longer done with a STS Reference. Instead, the Identity and Access Tool has to be used. You can download it from Visual Studio Gallery. After installing it, you can start it in the Solution Explorer in the project’s context menu.
Since we want to connect to ACS, select the third option Use the Windows Azure Access Control Service:
Now we have to configure the providers. Just click on the Configure link in the middle:
The ACS namespace is easy to know. It’s just the name you chose. The management key is not so easy to find:
  • Open Windows Azure Portal
  • Manage your ACS namespace
  • Select Management service
  • Click ManagementClient
  • Click Symmetric Key
Here you can copy the key:
The next step is to select the providers you want to use in your application. And to specify realm and return url of your application. By default the are initialized with the url used for debugging in Visual Studio.
After clicking OK, you get a lot of stuff generated in your Web.config. Additionally, you can find in the Azure Portal an additional Relying Party Application (originally, this was step 4 from How to: Create My First Claims-Aware ASP.NET Application Using ACS.
When you start now the debugger, the first page is something like
The user can select with which identity provider she wants to use. After the login, the start page of your application should be displayed, showing the user name.

Fiddler

When you check the logon process in Fiddler, you will see the following requests:
  • http://localhost:58235/
    The URL of the application itself, redirects to
  • https://markus.accesscontrol.windows.net/v2/wsfederation?...
    The page to select the identity provider; selecting one forwards to
  • https://accounts.google.com/o/openid2/auth?...
    The login itself, here at Google; after confirmation redirects back to
  • https://markus.accesscontrol.windows.net/v2/openid?...
    ACS redirects back to the application itself
  • http://localhost:58235/

Claims

Since .NET 4.5 every Principal is based on a ClaimsPrincipal. That means every attribute of the user is a claim. It’s easy to query or display them:
System.Security.Claims.ClaimsPrincipal cp = (System.Security.Claims.ClaimsPrincipal)User;
foreach (var claim in cp.Claims)
{
}
With a Goggle account par example, you have at least for claims:

Specific claims

You can rely only on the existence of the claims http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider and http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier. All other claims are optional, also such convenient things like http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name or http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress. Par example Windows Live ID does not provide this information due to security restrictions.
This is especially boring, since the name claim will be mapped to User.Identity.Name. When the claim is missing, the Name property is null. So maybe you get runtime errors because of that. Therefore it could be a good idea to provide a default value for this claim.
You can do it in the Windows Azure Portal in the section Rule groups. Select the rule group of your application, and then you should see Passthrough rules, which forward the claims from the identity provider to your application. Here you add your own rules, e.g.:
  • Identity Provider: Windows Live ID
  • Input claim type: Any
  • Input claim value: Any
  • Output claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
  • Output claim value: ???
  • Description: Default name for Windows LiveID
With this rule in place, every user authenticated with Windows Live ID has the name ???.

Roles

Also roles are now claims. That means you can also define a rule to apply a role:
  • Identity Provider: Windows Live ID
  • Input claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
  • Input claim value: (the name identifier)
  • Output claim type: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
  • Output claim value: admin
  • Description: Admin role for xxx
Now you can check the existence of the role, e.g. (or 1 one of the other 1,000,000 possibilities):
User.IsInRole("admin")

ClaimsAuthenticationManager

Adding roles or names via rules is not very feasible when you have more than 2 or 3 users. Therefore it is better to implement a ClaimsAuthenticationManager to extend the claims processing pipeline. Here you can modify the claims as you want:
public class MyClaimsAuthenticationManager : ClaimsAuthenticationManager
{
  public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
  {
    if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated)
    {
      ClaimsIdentity claimsIdentity = (ClaimsIdentity)incomingPrincipal.Identity;
      string identityProvider =
        claimsIdentity.Claims
          .Where(c => c.Type == "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider")
          .Select(c => c.Value)
          .FirstOrDefault();
      string nameIdentifier = 
        claimsIdentity.Claims
          .Where(c => c.Type ==ClaimTypes.NameIdentifier)
          .Select(c => c.Value)
          .FirstOrDefault();

      if (identityProvider == "uri:WindowsLiveID" && nameIdentifier == "FVUzvNwYGuC5cG4VYdWArf81SRj0QISjQpUIhaHonNE=")
      {
        claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, "Markus Wagner"));
        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
      }
    }

    return incomingPrincipal;
  }
}
Finally you have to configure your application to use the new ClaimsAuthenticationManager. This can be done in the Web.config:
<system.identityModel>
  <identityConfiguration>
    <claimsAuthenticationManager type="AcsAuthentication.MyClaimsAuthenticationManager, AcsAuthentication" />
  </identityConfiguration>
</system.identityModel>
For sure, in a real world application, you will not hard-code the claims here. Instead you will take them from a configuration file or a database. But the principle will stay the same.

Conclusion

For authentication, ACS is already a good alternative. Especially since you do not have to manage passwords.
With authentication it gets more complicated. Some prerequisites exist, but it isn't really comfortable.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.