Gérer la mobilité des internes sur SharePoint (et autres), avec Azure AD Application Proxy

Dans un contexte de mobilité et BYOD, on désire de plus en plus ouvrir l’accès aux ressources « on premise » en dehors de l’entreprise. Si ADFS reste la référence pour l’ouverture aux externes, l’équipe Azure de MS a développé récemment un proxy applicatif permettant de se connecter en SSO  sans mise en place d’infrastructure ou reconfiguration de l’application « on premise ».

Votre application (ici un portail SharePoint) est tout simplement publiée comme une APP sur votre portail 365. L’authentification est transparente, en interne comme en mobilité.

On peut, bien sur, panacher avec ADFS pour les « vrais » externes qui n’auraient pas de compte dans un AD approuvé.

Le Principe

Dans le cas d’un utilisateur externe, on ne peut pas recourir à l’authentification intégrée sans VPN, et on recourt généralement à ADFS :

auth1

Si l’utilisateur externe (ou en mobilité) est référencé dans un annuaire AD, il existe une alternative à ADFS, présentée dans le cadre de ce post :

auth2

On évite ainsi :

  • De déployer ADFS
  • De recourir à un formulaire d’authentification ou autre custom provider

Le pré-requis est d’avoir synchronisé les utilisateurs avec notre tenant 365. On centralise alors l’authentification sur le tenant, et cette identitée « online » pourra être convertie en SSO en une identité interne via ce proxy applicatif.

Par exemple, dans le cas de l’authentification windows intégrée, « eissaly@mcnext.com » peut être utilisé comme « MCNEXT\Emmanuel.issaly »

Configuration 365

Si votre annuaire est déjà synchronisé avec azure AD (c’est un prérequis), Vous pouvez sauter ce chapitre. Sinon, créez un tenant Office 365 de test (un tenant de dev MSDN suffit)

image007

Créez une adresse admin facile à mémoriser, ici j’ai pris admin@eissaly.onmicrosoft.com

Sur ce tenant « eissaly », allez activer Azure AD par la tuile d’admin :

image008

Vous devez synchroniser les utilisateurs de votre domaine local avec un domaine online. Dans mon cas, mcnext.com étant déjà pris, j’ai ajouté un suffixe UPN « @clouddetest.fr » à mon AD de tests sp2013.local. Emmanuel ISSALY est alors identifié par SP2013\eissaly *ou* eissaly@clouddetest.fr.

Pour déclarer et vérifier le domaine clouddetest.fr dans azure, allez dans « Domains » du tenant 365 :

image009

Une fois le domaine vérifié, on peut lancer la synchronisation d’un serveur quelconque de notre lab avec ADCONNECT. C’est en fait un FIM customisé (au sens qu’il fonctionne correctement). Il vous faudra le compte admin 365 et le compte admin du domaine local.

Licences

Pour que l’application proxy fonctionne, il faut une licence Basic ou Premium AAD pour chaque utilisateur.
Activez le premium trial, ce qui vous donnera 100 licences.

image012

Publication de l’application SharePoint

Publication azure

 Nous allons publier notre SharePoint on prem comme une APP dans 365 :
Allons dans « Applications » de notre Azure AD, et faisons « ADD »

image013

image014

Puis s’offre à nous l’option de télécharger ce nouveau connecteur :

image015

Déployez-le sur un serveur 2012 R2 qui a accès à internet. Le serveur de synchro qui fait déjà AD connect est une bonne destination pour lui !

Suite à l’activation de ce connecteur, on a d’autres options dans la configuration de notre application :

1.- Une URL publique générique, ou l’adresse publique de votre domaine :

image017

image018

2. et la patte interne, en windows intégré :

image019

à noter que l’équipe azure AD travaille sans cesse à proposer de nouveau mappings, et l’on peut même maintenant convertir l’identité online à une identité non windows (via la délégation Kerberos)

Configuration HTTPS / Kerberos

Pour que cela marche, il faut quand même quelques contraintes, en l’occurrence une délégation contrainte Kerberos (KCD)

  1. Créer le SPN du portail (nom court nom long et compte de pool d’appli)

setspn –S http/portal sp_app

setspn –S http/portal.clouddetest.fr sp_app

2. Autoriser la délégation KCD sur le serveur qui héberge le connecteur :

Sur un contrôleur de domaine, Propriétés sur l’ordinateur, onglet délégation, autoriser la délégation :

image020image021

Puis ajoutez le SPN (on le trouve par le compte de pool)

image022

Vous devriez à présent obtenir un ticket kerberos en appelant votre site SharePoint.
Attention, il faut que le DNS appellé soit une IP (A record), pas un alias !

Synthèse

Le site SharePoint étant déclaré sur le tenant, on peut y accèder par un clic de tout périphérique connecté à Internet :

image023

De ce fait, que l’on soit à l’intérieur du LAN, ou d’un appareil mobile (ici Chrome + Android) connecté à office online, l’expérience utilisateur est maintenant la même, quelque soit l’environnement
(pas de prompt, Windows intégré dans notre cas)

image025

 Comment ça marche

Cf https://msdn.microsoft.com/en-us/library/azure/dn879065.aspx

image026

  1. L’utilisateur tape l’url du site SharePoint
  2. AAD proxy fait de la pré-auth : si vous êtes déjà connecté online, on obtient un jeton, sinon il vous prompte pour votre profil online.
  3. Dans tous les cas, le jeton obtenu est envoyé au proxy.
  4. Le connecteur extrait du jeton l’UPN (lidentifiant utilisateur)
  5. Le connecteur fait une demande de jeton Kerberos de la part de l’utilisateur
  6. Si la delegation est autorisée, le jeton est émis.
  7. Le connecteur envoie alors la requete et son jeton comme si on était “interne »
  8. La page est renvoyée au navigateur

Il est également possible de customiser le ticket kerberos pour que le login arrivant sur l’application ne soit pas l’email, ou soit un login différent du login online (typiquement pour se connecter à un système non Windows) :

image027

Sur ce schéma, je me connecte entant que joe@contoso.com à 365, mais j’arrive en tant que Contoso\joed sur l’application ciblée par ce connecteur.

Hey Cortana, do you speak JavaScript?

In this article I will try to describe every steps to use Cortana with your JavaScript UWP app.

The first step is to create an xml file that represents the Voice Command Definition :

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet xml:lang="fr-fr" Name="VDM_fr-fr">
    <CommandPrefix> VDM, </CommandPrefix>
    <Example> Affiche les dernières VDM </Example>
    
    <Command Name="showlast">
      <Example> Affiche les dernières VDM </Example>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> Affiche [les] dernières </ListenFor>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> Affiche [mes] dernières </ListenFor>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> Ouvre [les] dernières </ListenFor>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> montre [moi] [les] dernières </ListenFor>
      <Feedback> affichage des dernières VDM </Feedback>
      <Navigate />
    </Command>
    <Command Name="showcategorie">
      <Example> Affiche les VDM de travail</Example>
      <ListenFor RequireAppName="ExplicitlySpecified"> ouvre les {builtin:AppName} [de] {cat}</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> affiche les {builtin:AppName} [de] {cat}</ListenFor>
      <Feedback> ouverture des VDM de {cat}</Feedback>
      <Navigate />
    </Command>

    <PhraseList Label="cat">
    </PhraseList>
  </CommandSet>
  <CommandSet xml:lang="en-us" Name="VDM_en-us">
    <CommandPrefix> FML </CommandPrefix>
    <Example> Show me the latest </Example>

    <Command Name="showlast">
      <Example> Show me the latest </Example>
      <ListenFor RequireAppName="AfterPhrase"> show [me] the latest </ListenFor>
      <ListenFor RequireAppName="AfterPhrase"> open the latest </ListenFor>
      <ListenFor RequireAppName="AfterPhrase"> display the latest </ListenFor>
      <Feedback>  display of the last FML</Feedback>
      <Navigate />
    </Command>

    <Command Name="showcategorie">
      <Example> Displays the FML of love </Example>
      <ListenFor RequireAppName="ExplicitlySpecified"> Opens the  {builtin:AppName} [of] {cat}</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> Displays the {builtin:AppName} [of] {cat}</ListenFor>
      <Feedback> opening FML of {cat}</Feedback>
      <Navigate />
    </Command>

    <PhraseList Label="cat">
    </PhraseList>
  </CommandSet>

</VoiceCommands>
  • In this file the root element is the VoiceCommands Element, it’s contains a list of commandSet elements. Each commandSet is for a language.
    • An commandSet contains a list of command (and others things …)
      • A command is a “command” and contains an example, and multiple elements of ListenFor, a feedback element, and an instruction element (navigate in the first sample) that explicitly specifies that this command will navigate to your app.
        • ListenFor is the command phrase, it has a RequireAppName attribute that specifies where the app name can appear in the voice command.
      • A PhraseList that contains multiple item, each Item specifies a word or phrase that can be recognized to initiate the command that references the PhraseList (optional). You can optionnaly load dynamically a list of items from your code.

You have to be very careful, when you write this file! If you don’t respect the structure or If you miss an element, the Cortana API will fall in error without any information*.

The final step.
Instantiate your new XML file of VCD, in your JavaScript code.

In this following code, I call initCortana function to initialize the VCD file using de VoiceCommandDefinitionManager API located in the Windows.ApplicationModel.VoiceCommands namespace.

You have to pass your xml file to the « installCommandDefinitionsFromStorageFileAsync » function. If everything it’s not OK the callback of the promise returns a error and you pass by:

console.error(‘error file vdmvoicecommands.xml’, er);

In this case: check and re check an re re re check your VCD file 🙂

If the file was initialized correctly, you could add a list of items to populate the phrase lists.



    var wap = Windows.ApplicationModel.Package;
    var voiceCommandManager = Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager;

    var initCortana = function (categories) {
        categories = categories || [];
        return wap.current.installedLocation.getFileAsync("vdmvoicecommands.xml").then(function (file) {
            return voiceCommandManager.installCommandDefinitionsFromStorageFileAsync(file);
        }, function (er) {
            console.error('error file vdmvoicecommands.xml', er);
        }).then(function () {
           var language = window.navigator.userLanguage || window.navigator.language;

            var commandSetName = "VDM_" + language.toLowerCase();
            var commansets = Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.installedCommandDefinitions;
            if (commansets.hasKey(commandSetName)) {
                var vcd = commansets.lookup(commandSetName);
                var phraseList = [];
                categories.forEach(function (c) {
                    phraseList.push(c.title);
                });
                return vcd.setPhraseListAsync("cat", phraseList).then(function () {
                    console.log("VCD loaded !");
                 }, function (er) {
                    console.error('error set phraseList', er);
                })
            } else {
                console.warning("VCD not installed yet?");
            }
        }, function (ee) {
            console.error("installCommandDefinitionsFromStorageFileAsync error", ee);
        });
    }

Now you have to handle the activation event that will get sent to your app, and parse arguments.

  app.addEventListener("activated", function (args) {
        var appLaunchVoiceCommand = activation.ActivationKind.voiceCommand || 16;
        if (args.detail.kind === appLaunchVoiceCommand) {
            return handleVoiceCommand(args);
        }
    });

The handleVoiceCommand function parse the args passed from the activated app event and do the navigation to the right place

    var app = WinJS.Application;
    var commands = {
        "showlast": function (commandresult) {
            return WinJS.Navigation.navigate(VDM.Pages.VDMList, { viewType: 'last', viewLabel: WinJS.Resources.getString('appbar_views_last') });
        },
        "showcategorie": function (commandresult) {
            var categorie = commandresult.semanticInterpretation.properties.cat[0];
            return WinJS.Navigation.navigate(VDM.Pages.VDMList, { viewType: categorie.toLowerCase(), viewLabel: categorie });
        }
    }
    var handleVoiceCommand = function(args) {
        if (args.detail && args.detail.detail) {
            var voicecommand = args.detail.detail[0];
            if (voicecommand) {
                var result = voicecommand.result;

                if (result) {
                    var commandname = result.rulePath ? result.rulePath[0] : '';
                    var properties = result.semanticInterpretation ? result.semanticInterpretation.properties : {};
                    console.log("voice activation (" + commandname + ") confidence: " + result.rawConfidence, result);
                    var cmd = commands[commandname];
                    if (cmd) {
                        return cmd(result).then(function () {
                            VDM.Utils.loadMainAds();
                        });
                    }
                }

            }
        }
    }
    app.addEventListener("activated", function (args) {
        var appLaunchVoiceCommand = activation.ActivationKind.voiceCommand || 16;
        if (args.detail.kind === appLaunchVoiceCommand) {
            return handleVoiceCommand(args);
        }
    });

Let’s talk more with the app (with appService)

If you need a deeper integration with Cortana, you could also « talk » with her using an app service.

An app service is a Background task that Cortana could call when you use a command. You will have to explicitely declare which service Cortana must call in your command file.

    <Command Name="getFML">
      <Example> tell me a FML </Example>
      <ListenFor RequireAppName="ExplicitlySpecified"> tell me a {builtin:AppName} </ListenFor>
      <Feedback> Here an FML </Feedback>
      <VoiceCommandService Target="FMLVoiceCommandService"/>
    </Command>

Now let’s implement the Application Service. You must add it to your application manifest by pointing to JavaScript file, and give it the name you use in the command file :

 <uap:Extension Category="windows.personalAssistantLaunch"/>
 <uap:Extension Category="windows.appService" StartPage="js/voiceCommandService.js">
    <uap:AppService Name="FMLVoiceCommandService"/>
 </uap:Extension>

Beware the visual studio appxmanifest editor, it removes this entry if anything changes in it (like a upgrade of version when you generate a new appx package) this bug will certainly be corrected in the update 1 of Visual Studio.

Now let’s create the javascript file and implement the service itself.

When you are using JavaScript App Services are a lot like independant web workers. You can import all the JS file you need to run your code by using importScripts

importScripts("/WinJS/js/base.js");
importScripts("/js/FML.API.js");

The service is loaded by cortana, so when is loaded the doWork function is called.
If the Windows.UI.WebUI.WebUIBackgroundTaskInstance.current.triggerDetails is an instance of Windows.ApplicationModel.AppService.AppServiceTriggerDetails, we can get voice command used to launch this task and do things like:

  • Send an waiting response message
  • displays items
  • Send a final response message
var appService = Windows.ApplicationModel.AppService;
var backgroundTaskInstance = Windows.UI.WebUI.WebUIBackgroundTaskInstance.current;
var details = backgroundTaskInstance.triggerDetails;
var deferral = backgroundTaskInstance.getDeferral();

if (details instanceof appService.AppServiceTriggerDetails) {
    voiceServiceConnection = voiceCommands.VoiceCommandServiceConnection.fromAppServiceTriggerDetails(details);
    voiceServiceConnection.addEventListener("voiceCommandCompleted", onVoiceCommandCompleted);

    voiceServiceConnection.getVoiceCommandAsync().then(function completed(voiceCommand) {

    // here you can check the voiceCommand, call an API (or read a file) and send messages to Cortana UI

            var userMessage = new voiceCommands.VoiceCommandUserMessage();
                    userMessage.spokenMessage = "I'm Cortana and I read this awesome message";
                    userMessage.displayMessage = "I'm Cortana and I read this awesome message";
            var response = voiceCommands.VoiceCommandResponse.createResponse(userMessage);
            return voiceServiceConnection.reportSuccessAsync(response);

    });
}

The displayMessage string must not exceed 256 character

And now, with this, you can ask Cortana: « hey cortana, tell me a FML »

#UWPHTML – using application insights in your Windows web applications

Publishing an application is a great experience. You really feel self-accomplishement when users provide good feedback (and ratings if you are Lucky). And then a few users reports errors but you can’t reproduce them. When your application is in the wild, you realize very quickly that you need something to get feedback from your app. Knowing what your users are doing with your application is also very valuable to help you focus efforts where you need to.

Without noticing you slipped from developping a hobby application to entering the world of analytics. You know, those little nasty snippets that your customers always asked you to put all around like Xiti or Google analytics.

Microsoft has it’s own solution for application analytics called Azure Application Insights. This tool has free and paid options depending on how long you store data, and the number of events you want to manage.

When you create a C# UWP application, Visual Studio has an option to integrate Application Insights for your project.

Unfortunately, you don’t have that same option when using UWP in HTML/JavaScript. In that case, you will have to do all plumbing manually. Microsoft provide a SDK for using Application Insight in JavaScript. It works well but this SDK is really oriented toward web applications, both in the sdk and in the structure of the Dashboard. Even worse, if you choose to type your application insights instance to a Windows application, some of the events (like errors…) does not show up in the Dashboard when sended with Javascript SDK.

For « Bring the popcorn« , it really is a Windows native application and we wanted to get the appropriate Dashboard and reporting. After some time spent with Fiddler comparing items sent to app insight from C#  and javascript, we narrowed down the différences in metadata.

We wrote small a wrapper for JavaScript sdk that adds those metadata and everything starts working fine.

This wrapper is published in WinJSContrib but we made it in such a way that it will work without WinJS or WinJSContrib.

Using application insights in your Windows Web Application is now very easy. You will first need to declare your application in application insights. When declaring your application, choose « Windows store application » for application type. When your instance is ready, go to it’s properties and get the « instrumentation key ». You will need this key in your application.

Now you are ready to integrate App Insights into your application. Go grab Application Insight Javascript SDK, and our wrapper, and add reference to them in your application.

In your application bootstrap code, you could now add something like this :


var appInsight = new WinJSContrib.WinRT.AppInsight({ instrumentationKey: "yourInstrumentationKey" });
appInsight.tracker.trackEvent("app start");

The wrapper will inject the appropriate metadata to have your events displayed in the Dashboard. It also registers to global « error » events, and send the corresponding errors to application insights.

If you are using WinJS, you could also have page navigation events for free :


appInsight.wrapWinJSNavigation();

If you want to see a real world example, you could check how we use this in Bring the popcorn.

#UWPHTML – Building Windows 10 web applications

Many people still ignores it, but since Windows 8 (and 8.1 for Windows Phone), you can build Windows native applications using HTML and JavaScript. Yes you read it correctly NATIVE applications. Those applications does NOT run through a webview but with a native HTML/JavaScript application shell called « wwahost ». Those applications are now called « Windows web applications ».

Windows 10 introduced a few enhancements to Windows web apps. They run with a different security context and on Microsoft Edge engine. There is also something called « project Westminster ». More simply, Westminster is « hosted applications », or applications that runs with content hosted on the internet but still have access to Windows APIs. You could find more about hosted apps on our blog, or obviously on Microsoft’s blog.

Windows web apps are fully parts of the new concept of Universal Windows Platform applications. It means that you could make applications using HTML/JavaScript for Windows desktop and tablet, but also for Windows 10 mobile, Xbox, Windows IoT, and the soon to come Hololens.

This post is the first of a serie where we will talk about various aspects of Universal Windows web apps using HTML and JavaScript. We will illustrate the different topics with a real application called « Bring the popcorn », a remote controller for a Kodi or Xbmc media server.

This application is open source, and available in the Windows store. It uses WinJS and WinJSContrib to provide a fast and fluid experience, using the latest features of Edge and Chakra engine (or at least those made available in web apps…).

Hope you will enjoy this serie !