Client-Side benaderen van de Access Control Service

De access control service voor Windows Azure is al geruime tijd beschikbaar voor gebruik in een applicatie. Hierdoor is er al genoeg informatie te vinden over wat de service wel en niet kan betekenen op het gebied van beveiliging. Ik ga daarom in deze post niet in op hoe je de service configureert en wat de verschillende onderdelen van service betekenen. Ik ga echter dieper in op hoe je de geconfigureerde identity providers eenvoudig binnen het domein van een website kunt tonen. De website in dit voorbeeld is gebouwd met behulp van MVC 3. We hebben de access control service als STS referentie aan onze applicatie gekoppeld en geconfigureerd met vier authenticatie providers: Windows Live ID, Yahoo!, Google en Facebook.

Out of the box

Nadat deze koppeling is gerealiseerd zullen gebruikers voor authenticatie naar een standaardpagina van de access control service gestuurd worden. Deze pagina past echter niet snel binnen de stijl van de eigen site waardoor we de gebruiker niet de gewenste User Experience kunnen bieden. Gelukkig stelt de service ontwikkelaars in staat om deze pagina te downloaden en aan te passen voor betere integratie met de site.
 
De standard pagina van de service past niet snel binnen de stijl van een website
 
 
Figuur 1. Standaard login pagina van ACS
 
Om deze code te downloaden kunnen we inloggen op de management portal van de service. Deze is normaal gesproken te benaderen via https://<servicenamespace>.accesscontrol.windows.net en kan gebruikt worden door de Windows Live ID’s die zijn aangemerkt als portal administrator. Eenmaal ingelogd kiest u voor Application Integration aan de linkerkant van de pagina. Op de Application Integration pagina klikt u op de “Login Pages” link.
 
De link brengt ons op een pagina met een overzicht van alle applicaties die gebruik maken van deze instantie van de service. We kunnen de integratie details van deze applicaties bekijken door op de naam van de applicatie te klikken. In de details van de applicatie zien we twee opties. Optie 1 is voor het gebruik van login pagina die wordt gehost door de service zelf. Optie 2 geeft de mogelijkheid om de pagina te downloaden zodat deze gehost kan worden in de site. We kiezen voor Optie 2 en slaan de login pagina op binnen onze site en we verwerken de code van de pagina later in een view van onze site.

Autorisatie in MVC

Nu we de pagina zelf hosten moeten we gebruikers niet doorsturen naar de standaard pagina maar naar de eigen pagina binnen onze website. Normaal gesproken zijn pagina’s beveiligd met het Authorize attribuut op een Controller of een Action (Listing 1).

//standaard
public class ForumController : Controller
{
   [Authorize]
   public ActionResult Index()
   {
        ViewBag.Message = "Welcome to e-reader forum";
 
        return View();
   }
}

Listing 1. Standaard authenticatie attribuut

Om gebruikers door te sturen naar een eigen pagina moeten we een eigen attribuut schrijven die de autorisatie en het doorsturen verzorgd. Een eenvoudige uitwerking van een dergelijke class is te zien in Listing 2. Voor onze aangepaste autorisatie erven we van FilterAttribute en implementeren we de IAuthorizationFilter interface.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class FederatedAuthorizationFilterAttribute
                       : FilterAttribute, IAuthorizationFilter
    {
       
        public void OnAuthorization(
                       AuthorizationContext filterContext)
        {
            if (!filterContext.IsChildAction)
            {
                var returnUrl =
                        filterContext.RequestContext
                              .HttpContext.Request.RawUrl;
               
                if (!filterContext.RequestContext
                              .HttpContext.User.Identity
                              .IsAuthenticated)
                {
                    var realm = FederatedAuthentication
                          .WSFederationAuthenticationModule.Realm;
                    var wreply =
                     "https://ereaders.sparked.nl/Account/LogOn/";
                
                    var loginPage = string.Format(
                                    CultureInfo.InvariantCulture,
                                    "~/Account/LogOn?realm={0}&
                                     wreply={1}&returnUrl={2}",
                                    realm,
                                    wreply,
                                    returnUrl);
                    filterContext.Result =
                              new RedirectResult(loginPage);
                }
            }
        }
 
//custom
public class ForumController : Controller
{
   [FederatedAuthorization]
   public ActionResult Index()
   {
        ViewBag.Message = "Welcome to e-reader forum";
 
        return View();
   }
}

Listing 2 Custom autorisatie attribuut.
In de laatste twee regels van dit code voorbeeld wordt de url opgebouwd waar de gebruiker naar wordt doorgestuurd. In de url geven we een aantal parameters mee, die we straks weer gebruiken in de view. Voor de login pagina hebben we een controller,  een model en een view nodig. De Action en het model zijn weergegeven in Listing 3. Nu rest ons enkel nog de view om de login opties te tonen.

Public ActionResult LogOn(string realm, string wreply,
                          string returnUrl)
{
   var model = new LogOnModel
   {
     AcsNamespace =
           "<uwservicennamespace>.accesscontrol.windows.net”
     Realm = Uri.EscapeDataString(realm),
     ReplayTo = Uri.EscapeDataString(wreply),
     Context = Uri.EscapeDataString(returnUrl)
   };
  
   return View(model);
}
 

Listing 3. Controller method en model

Login View

Als we de standaard code gebruiken die we van de service hebben gedownload, dan zullen we zien dat deze code vrij complex is opgebouwd en daardoor ook lastig is aan te passen. Er wordt html opgebouwd met JScript waarmee elementen en CSS settings worden aangemaakt. Dit alles zorgt voor code die lastig te onderhouden en aan te passen is.
 
Om deze code te vereenvoudigen gaan we in deze view gebruik maken van een aantal javascript plug-ins. Allereerst gebruiken we knockoutjs (http://knockoutjs.com/ ) om een viewmodel in JScript te maken waarin we de identity providers laden.  Daarnaast gebruiken we een JQuery plug-in waarmee we een template kunnen maken voor een identity provider (http://api.jquery.com/jquery.tmpl/ ). Dit template wordt later automatisch samengevoegd met de data dat in ons viewmodel wordt opgeslagen. Alle code in de view is weergeven in Listing 4. Ik zal door alle stappen heen zal lopen om ze toe te lichten.

<h2>Logon</h2>
 
<!--stap 3 -->
<ul data-bind='template: {
             name: "providerTemplate", foreach:providers() }' />    
 
<!--stap 4 -->
<script type="text/html" id="providerTemplate">
 <li>
    <a href="${ LoginUrl }">
      {{if ImageUrl != "" }}
        <img src="${ ImageUrl }"
                style="height:100px;" alt="${ Name }" />    
      {{else}}
        ${ Name}
      {{/if}}
   </a>
 </li>
</script>
 
<!--stap 2 -->
@section Scripts
{
   <script
   src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"
   type="text/javascript"></script>
 
   <script
   src="@Url.Content("~/Scripts/jquery.tmpl.min.js")"
   type="text/javascript"></script>
  
   <script
   src="@Url.Content("~/Scripts/knockout-1.2.1.js")"
   type="text/javascript"></script>
 
    <script language="javascript" type="text/javascript">
        var viewModel = {};
        viewModel.providers = ko.observableArray([]);
 
        ko.applyBindings(viewModel);
      
        function acsCallback(json) {
            for (var i in json) {
                viewModel.providers.push(json[i]);
            }
        }
    </script>
 
<!--stap 1 -->
 
<!--script om acs aan te roepen -->
    <script src="https://@Model.AcsNamespace:443/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm=@Model.Realm&reply_to=@Model.ReplayTo&context=@Model.Context&version=1.0&callback=acsCallback" type="text/javascript"></script>
}

Listing 4. Custom View
 
In stap 1 voegen we een link naar een JScript resource die wordt gehost op de service. Dit script geeft data terug in het JSON formaat en we kunnen het script voorzien van een callback via de querystring. We geven de callback parameter de naam van de functie mee die moet worden aangeroepen nadat de JSON is ontvangen. In ons voorbeeld heet deze functie acsCallback en wordt beschreven in stap 2.
 
In stap 2 beschrijven we het JScript viewmodel en de callback functie voor het verwerken van de resultaten uit stap 1. Het viewmodel bestaat uit een eenvoudig JScript object met 1 property. De property wordt aangemaakt als een lege Observable Array. Een Observable Array is 1 van de types die worden geïntroduceerd door KnockoutJS. Dit type is een array waarbij events worden aangeroepen bij het wijzigen van de inhoud. KnockoutJS zorgt er daarna automatisch voor dat de UI wordt bijgewerkt bij een wijziging van de array. De wijzigingen aan de inhoud van ons viewmodel worden gedaan door de callback functie die beschreven is onder het viewmodel in stap 2. Deze functie loopt door de data heen die wordt teruggegeven door de service en voegt ze toe aan ons viewmodel. Wat ons nu nog rest is het koppelen van ons viewmodel aan de UI waardoor we de gegevens die we ontvangen van de service op een eenvoudige manier kunnen omzetten naar html.
 
Voor het koppelen van het viewmodel aan de HTML maken we bij stap 3 een UL tag aan en geven de tag een data-bind attribuut mee. KnockoutJS gebruikt dit attribuut op een willekeurige tag voor het koppelen van onderdelen van ons viewmodel aan tags in de HTML. We effectueren de koppeling door de applyBindings functie van KnockoutJS aan te roepen. Als we het data-bind attribuut nader bekijken dan zien we dat we KnockoutJS de opdracht geven om voor elke provider van ons viewmodel een template te gebruiken.
 
Het template beschrijven we aan de hand van de richtlijnen die ons worden geboden door de JQuery.tmpl plug-in. De plug-in stelt ons in staat om standaard HTML te schrijven en dit te gebruiken als template voor JSON data. Het template dat beschreven is in stap 4 is een voorbeeld van een dergelijk template. De data die in een attribuut van een identity provider is opgeslagen kunnen we tonen door de ${ } notatie te gebruiken. Daarnaast biedt de plug-in ons een aantal extra tags waarmee we functies en condities kunnen gebruiken om de HTML op te bouwen. In ons voorbeeld gebruiken we de {{if}} functie om te kijken of we een plaatje hebben geconfigureerd voor deze provider. Als dat het geval is, tonen we het plaatje anders wordt naam van de provider gebruikt als link. Een voorbeeld van hoe een eenvoudige login pagina er uit kan zien is te zien in Figuur 2.
 
  
Figuur 2. Eenvoudig login scherm

Conclusie

Nu ons template is beschreven hebben we alle stappen doorlopen om de code te vereenvoudigen. De inlog opties worden nu getoond aan de hand van het template waardoor het maken van een geavanceerd keuze menu eenvoudiger is geworden. Het gebruik van KnockoutJS en JQuery templating beperkt zich niet alleen tot dit voorbeeld maar er zijn een aantal dingen waar rekening mee gehouden dient te worden.
 
Het gebruik van templating maakt het lastiger om de pagina’s geschikt te maken voor zoekmachines. Voor de login pagina is dit geen probleem maar bij grootschalig gebruik op publieke pagina’s zal hier rekening mee gehouden moeten worden. Daarnaast is de volgorde waarin de plug-ins worden geladen van belang. Het is belangrijk dat eerst JQuery, dan de templating en dan pas KnockoutJS wordt geladenIndien KnockoutJS wordt gebruikt in een pagina met een XHTML of HTML 4 doctype dan zal deze pagina niet door de validatie van W3C heen komen. Het gebruik van data-* attributen is echter volledig ondersteund in HTML 5 waardoor deze plug-in voor nieuwe websites een belangrijke toevoeging kan zijn.
Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar