It’s been two weeks already that we’ve got back from LA after attending PDC. Lots of things announced there.

Microsoft Argentina organized the local-mini version of PDC. I will be there showing Windows Azure with Edgardo.

image

The talk will be mainly demos (as usual :) and explain some concepts around Windows Azure.

You can register here (it seems it’s all booked though): http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032394696&Culture=es-AR

Other southies will be presenting as well at this event:

See you there…

In the previous post I introduced a scenario where you can use .NET Services Access Control and Windows LiveID to delegate authentication and authorization. In this post we will go through the different pieces needed in the application to perform authorization checks. First thing will be configure the passive federation using Geneva on the application and later we will create an ASP.NET MVC action filter to perform the access check against the incoming claims.

Note: all the code showed here is using Microsoft Identity Framework "Zermatt" Beta 1. The new Geneva Framework might have some changes.

Configuring passive federation on the website

Configure passive federation on the website is about defining which SAML token version we will accept and the certificate we will use to decrypt the incoming token. The following configuration uses Zermatt Beta 1, so this probably changes on Geneva.

<microsoft.identityModel>
    <tokenHandlers>
      <remove type="Microsoft.IdentityModel.Tokens.Saml11.Saml11TokenHandler, Microsoft.IdentityModel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add type="Microsoft.IdentityModel.Tokens.Saml11.Saml11TokenHandler, Microsoft.IdentityModel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <samlSecurityTokenRequirement>
          <allowedAudienceUris>
            <add value="http://localhost/YourApp/" />
          </allowedAudienceUris>
        </samlSecurityTokenRequirement>
      </add>
    </tokenHandlers>
    <federatedAuthentication enabled="true">
    </federatedAuthentication>
    <serviceCertificate>
      <certificateReference findValue="01 20 …" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
    </serviceCertificate>
  </microsoft.identityModel>

When the user click on the sign in button, the link will point to to the .NET Services Access Control passive STS url. The following method uses Geneva to generate this WS-Federation url.

private static string GetFederationUrl(string realm, string issuer, string homeRealm, string returnUrl)
{
    FederatedAuthenticationModule fam = new FederatedAuthenticationModule();
    fam.Realm = realm;
    fam.Issuer = issuer;
    fam.Reply = returnUrl;
    SignInRequestMessage signInMsg = fam.CreateSignInRequest();
    signInMsg.Parameters.Add("whr", homeRealm);
    string url = signInMsg.WriteQueryString();
    return url;
}

The following code and configuration will give you an idea of the url that is being built. Pay attention to this url because a small change might break the whole thing.

string url = GetFederationUrl(ConfigurationManager.AppSettings["AccessControlRealm"],
                            ConfigurationManager.AppSettings["AccessControlIssuer"],
                            ConfigurationManager.AppSettings["AccessControlHomeRealm"],
                            replyTo);
<!– Windows Azure Federation –>
<add key="AccessControlRealm" value="http://localhost/YourApp/"/> <!– should match to a scope –>
<add key="AccessControlIssuer" value="https://accesscontrol.windows.net/passivests/yoursolution/LiveFederation.aspx"/>
<add key="AccessControlDefaultReply" value="http://localhost/YourApp" />
<add key="AccessControlHomeRealm" value="http://login.live.com" />

The AccessControlRealm config is important because it will match the scope on your solution. You will have to configure the scope to encrypt with the public key of your website certificate and create the claim mappings from Windows LiveID to your well known claims. If you don’t have the scope created or configured to output at least one claim you will get a 403 Forbidden on the .NET Services Access Control STS.

image

Performing access check in the web site

Now that we have everything configured and the token should be coming back to our website, it’s time to do the access check. By using Geneva, the token will be transformed to a Principal object and it will be accessed through the ClaimsPrincipal static class. On the other hand, ASP.NET MVC allow us to plug into the action execution pipeline and get access to the context data like route values. The following code shows an ActionFilterAttribute that will grab the claims from the the Geneva ClaimsPrincipal and will call a strategy class that will perform the access check. If the access check is not successful, the filter will render a NotAuthorized view.

namespace YourApp.Identity
{
    using System;
    …

    public class ClaimAuthorizationRouteFilterAttribute : ActionFilterAttribute
    {
        public ClaimAuthorizationRouteFilterAttribute(string[] operations)
        {
            this.Operations = operations;
        }

        public string[] Operations { get; set; }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var identity = ClaimsPrincipal.Current.Identity as IClaimsIdentity;
            var claims = identity.Claims.ToArray();
            var routeData = context.RouteData.Values.ToArray();
            var strategy = CreateAuthorizationStrategy();
            var executionContext = new ExecutionContext()
            {
                ClaimsNeeded = Operations,
                OperationContextData = routeData,
            };

            if (!strategy.IsAuthorizedFor(executionContext, claims))
            {
                context.Result = new ViewResult
                {
                    ViewName = "NotAuthorized"
                };
            }

            base.OnActionExecuting(context);
        }

    }
}

Finally, the following code shows an implemented strategy for a multi tenant application that manage projects.

namespace YourApp.Identity
{
    using System.Linq;
    using System;

    public class StandardAuthorizationStrategy : IAuthorizationStrategy
    {
        private const string ProjectClaimType = "urn:Project";
        private const string TenantClaimType = "urn:Tenant";
        private const string OperationClaimType = "urn:Operation";

        public bool IsAuthorizedFor(ExecutionContext context, Microsoft.IdentityModel.Claims.Claim[] claims)
        {
            bool authorized = true;
            var tenantClaim = claims.SingleOrDefault(c => c.ClaimType == TenantClaimType);
            var operationClaims = claims.Where(c => c.ClaimType == OperationClaimType);
            var projectClaims = claims.Where(c => c.ClaimType == ProjectClaimType);
        var tenant = context.OperationContextData["Tenant"].ToString();
        var project = context.OperationContextData["Project"].ToString();

            if (!string.IsNullOrEmpty(tenant))
            {
                authorized &= tenantClaim.Value.Equals("*", StringComparison.OrdinalIgnoreCase) ||
                              tenantClaim.Value.Equals(tenant, StringComparison.OrdinalIgnoreCase);
            }

            if (!string.IsNullOrEmpty(project))
            {
                authorized &= projectClaims.Where( p => p.Value.Equals("*", StringComparison.OrdinalIgnoreCase)).Count() > 0 ||
                              projectClaims.Where( p => p.Value.Equals(project, StringComparison.OrdinalIgnoreCase)).Count() > 0;
            }

            if (context.Operations != null)
            {
                bool temp = true;
                foreach (string op in context.ClaimsNeeded)
                {
                    temp &= operationClaims.Where(o => o.Value.Equals(op, StringComparison.OrdinalIgnoreCase)).Count() > 0 ||
                            operationClaims.Where(o => o.Value.Equals("*", StringComparison.OrdinalIgnoreCase)).Count() > 0;
                }
                authorized &= temp;
            }

            return authorized;
        }
    }
}

The only thing left is to put an attribute above the action. The following attribute specifies that the New action will be executed if the incoming token contains the following "urn:Operation" claims.

public class ProjectsController : Controller
{

        [ClaimAuthorizationRouteFilter(new string[] {
                                            "AddUser",
                                            "AddUsersToProject",
                                            "CreateProject" })]
        public ActionResult New() {
        ….

    }

    …

}

So if a user browses to: http://yourapp/Contoso/Projecsts/New, the filter will call the strategy that will check:

  • if the user contains a tenant claim with the value "Contoso" (taken from the route data)
  • if the user contains three operation claims: AddUser, AddUsersToProject and CreateProject

And if a user browses to: http://yourapp/Contoso/Projecsts/some-project/Edit, the filter will call the strategy that will check:

  • if the user contains a "tenant" claim with the value "Contoso" (taken from the route data)
  • if the user contains the "operation" claims specified in the Edit action
  • if the user contains a "project" claim with the value "some-project"

image

The last couple of months together with other people at Southworks we’ve been working with the DPE team on samples, demos, hands on labs for PDC all related to the cloud computing services Microsoft announced at PDC, the Azure Services Platform. 

During the week, I attended Kim Cameron’s and Vittorio Bertocci session where they talked about identity federation and claim based architecture using "Geneva" Server, Microsoft Federation Gateway, "Geneva" Framework (previously known as Zermatt) and the .NET Services Access Control. I enjoyed watching Vittorio during the session. 

Other interesting things we did in the identity arena with Ryan Dunn is use the .NET Services Access Control and Windows Live ID to delegate authentication and authorization to the cloud. In this post I will introduce the scenario where you can federate your application against .NET Services Access Control which indeed federates against Windows LiveID. This will allow users of your application to log in to your application using their Windows LiveID account and then use .NET Services Access Control to transform the email claim to a set of claims to perform authorization access checks.

Identity + Access Control using Windows Live ID + .NET Services Access Control

Windows Live ID can authenticate users of your web site and then use .NET Services Access Control to map claims between the Live ID (email) and some other claim (like role, operation, task). The image below shows a claim mapping that you would create in your .NET Services account.

image

The output claims could be used later in the application to perform access check against resources or modify the UI according to the incoming claims. The flow is governed by the WS-Federation protocol as shown below:

 

image

In a nutshell, the browser will click on the Sign In link on the website and it will be redirected to the token issuer, in this case the .NET Services Access Control passive STS. The home realm on the url will be login.live.com and the .NET Services STS trust on Windows LiveID tokens. The user will log in on Windows LiveID and it will send the token back to the .NET Services STS. Finally the claim mapping will occur and the token will come back to the website with the authorization claims.

In the following post I will show how to configure your application to read the incoming token claims and do access check over page urls.