Loupe

Connecter une application Cordova aux API Microsoft Graph - partie 2

Dans un précédent article, nous avons vu comment déclarer une application OneDrive. Il est maintenant temps de passer à l'étape suivante de notre série : se connecter en Oauth depuis l'application Cordova. Le code qui suit est réalisé à l'aide du framework IONIC mais cela peut être réalisé aussi facilement dans une application Cordova "classique".

 

Pour rappel, les articles de la série sont les suivants :

 

Principe de connexion OAuth

Les différentes cinématiques de connexion possibles sont très bien décrites dans la documentation Microsoft dédié à Microsoft Graph. Dans notre cas nous allons choisir d'utiliser le Code Flow ce qui nous permettra notamment de faire des appels au fil du temps à l'API Microsoft Graph sans avoir à redemander à l'utilisateur de se connecter.

Dans ce cas de figure, on obtiendra un token / jeton d'accès qui permettra de faire des appels à l'API pendant une certaine durée (comme dans le cas du token flow) mais aussi un refresh token permettant d'en obtenir un nouveau après l'expiration du jeton initial. Cela donne un peu les jetons comme ça mais vous verrez, c'est simple !

Le fonctionnement se passe en plusieurs étapes que nous décrirons dans la suite de l'article :

  1. On demande à l'utilisateur son autorisation d'utiliser son identité dans le cadre d'un certain périmètre (compris dans celui défini dans notre premier article). Il sera sans doute amené à se connecter pendant cette étape.
  2. On récupère un code dans notre application.
  3. On échange ce code contre le jeton d'accès et de rafraîchissement.
  4. On peut ensuite appeler l'API en utilisant ces informations.

Cela est décrit dans le schéma innocemment emprunté à la documentation Microsoft :

authorization_code_flow.png

Demande d'autorisation à l'utilisateur

Cette étape est l'une des plus simple : il faut afficher une page web à l'utilisateur. Pour cela il y a plusieurs méthodes et composants possibles : nous choisirons d'ouvrir le navigateur vers la bonne page de connexion. Cela a l'avantage de lui permettre d'utiliser ses mots de passe enregistrés et s'il est déjà connecté à Microsoft de ne pas avoir à le refaire une seconde fois.

Pour ouvrir l'URL on utilisera le plugin CORDOVA InAppBrowser. L'installation est classique : on utilise NPM pour référencer le package, on ajoute InAppBrowser à la liste des providers et on va pouvoir demander l'ouverture de l'URL de connexion assez simplement. 

export class ImportPage {
	constructor(private iab: InAppBrowser) { }
	
	startOneDrive() {
	this.iab.create(
		onedriveStartUrl,
		"_system",
		{location: "yes"});
	}
}

 

Pour construire l'URL de connexion, on utilise les informations obtenues lors de l'enregistrement de l'application OneDrive. On prend soin de séparer les scopes par des espaces (%20), d'encoder l'URI de callback et de préciser que l'on veut un code en retour :

 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?'+
    'client_id=' + this.clientId +  
    '&scope=files.read%20files.readwrite.appfolder%20offline_access' +
    '&response_type=code'+
    '&redirect_uri='+ encodeURIComponent(this.callBackUri);

 IMG_0999.jpg

 

Récupération du code 

Pour récupérer le code, on va déclarer notre URI de callback comme protocole de l'application native Cordova générée. Ainsi lorsque la mire de connexion appellera l'URL de callback, ce sera notre application qui sera activée avec le code en paramètre de l'URI d'appel (par exemple : msalAppIdDeLaMort:///auth-redirect?code=df6aa589-1080-b241-b410-c4dff65dbf7c)! Sur iOS, Safari demandera à l'utilisateur de confirmer l'ouverture de l'application :

Image-1.jpg

Pour faire cette association de protocole, j'utilise le plugin Cordova cordova-plugin-customurlscheme facilement installable via la commande cordova adéquate :

cordova plugin add cordova-plugin-customurlscheme 
   --variable URL_SCHEME=msalappp-id-généré-lors-de-lenregistrement

 

Il faut ensuite être en mesure de récupérer le code au lieu de simplement activer l'application. Pour cela, j'utilise une méthode assez sale (mais bon après tout cela fait la puissance de Javascript #troll) : je déclare une méthode globale handleOpenURL dans le fichier index.html qui va renseigner une propriété de l'objet window. Cette méthode est appelée par le plugin lors de l'activation. Pour des raisons techniques sur iOS, je fais cette action au sein d'un timeout.

<script type="text/javascript">
  function handleOpenURL(url) {
    setTimeout(function () {
      window.ReceivedUrl = url;
      }, 0);
  }
  window.handleOpenURL = handleOpenURL;
</script>

 

Dans le composant ayant déclenché l'ouverture de la demande d'autorisation à l'utilisateur je vais alors ponctuellement regarder si une valeur a été renseignée sur window.ReceivedUrl. On déclare et utilise un Subject<string> pour faire apparaître ce code comme plus élégant. Aussi, il faut penser à proposer un bouton annuler à l'utilisateur dans l'interface au cas où il revienne dans l'interface sans compléter la connexion.

private checkProtocolArrival: boolean;
private protocolReturn = new Subject<string>();
private lastProtocolReturnSeen: string;

private startProtocolArrivalCheck() {
  if (!this.checkProtocolArrival) {
    return;
  }
  setTimeout(() => {
    const url = (<any>window).ReceivedUrl;
    if (url && this.lastProtocolReturnSeen !== url) {
      this.lastProtocolReturnSeen = url;
      this.protocolReturn.next(url);
    }
    this.startProtocolArrivalCheck();
  }, 300);
}

 

On pourra alors s'abonner à ce Subject<string> dans le constructeur de notre composant pour appeler une méthode permettant de finaliser notre processus de connexion et extraire le code :

this.protocolReturnSubscription = this.protocolReturn.subscribe(r => this.finalizeOneDrive(r));

 

Il ne nous reste alors plus qu'à extraire le code de l'URL d'activation de l'application à l'aide de ce code :

let codeStart = url.indexOf("code=");

if (codeStart > -1) {
  codeStart += "code=".length;
  const code = url.substr(codeStart);
}

 

Bonus : lorsque l'on développe l'application dans notre browser, il peut être compliqué de tester cette partie "activation d'une application". Il existe une solution simple qui consiste à écrire une application UWP qui va être activée sur notre poste et qui s'occupera de mettre l'URL d'activation dans le presse-papier Windows avec le code ci-dessous. Il faudra faire un champ spécifique dans l'application IONIC pour en permettre sa saisie mais cela fait déjà gagner beaucoup de temps. Pour information, il faut utiliser Edge (tiens, tiens, cela ne servirait pas qu'à télécharger Chrome :p ?) pour un fonctionnement idéal.

protected override void OnActivated(IActivatedEventArgs args)
{
    base.OnActivated(args);
    DataPackage dp = new DataPackage();
    string uri = (args as IProtocolActivatedEventArgs).Uri.OriginalString;
    dp.SetText(uri);
    Clipboard.SetContent(dp);
}

 

Échange du code contre un token d'accès

Une fois le code entre les mains, il reste à l'échanger contre un jeton d'accès en appelant l'endpoint Microsoft Graph dédié. On peut naturellement penser à utiliser l'HttpClient offert par Angular pour faire nos appels mais l'on va alors être confronté à une problématique : les CORS. Ces derniers vont gentiment nous empêcher d'appeler l'API avec ce petit message sympathique que l'on va vite apprendre à détester :

No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

 

Pour nos développements dans le navigateur, il existe des extensions Chrome (CORS Toggle par exemple) mais cela ne sera pas suffisant sur le device. La solution que j'ai finalement adoptée consiste à utiliser la couche native (💓) de chaque OS pour faire les appels HTTP car elle n'est pas limitée par CORS. Pour cela, j'utilise le plugin cordova-plugin-advanced-http au travers de Ionic native (ne pas oublier de mettre HTTP dans vos provider). Cela ne reste vraiment pas idéal car ce dernier n'est pas compatible browser et il faudra continuer d'utiliser HttpClient Angular classique pour les appels depuis le navigateur pendant vos développements.

L'utilisation du plugin se fait de cette manière :

  1. On spécifie que les paramètres seront à ajouter au format urlencoded,
  2. On créé le paramètre à envoyer à l'API Microsoft Graph conformément à la documentation,
  3. On effectue l'appel et on interprète le résultat pour y lire notamment le code d'accès.

Le code correspondant est le suivant :

const data = {
  client_id: this.clientId,
  redirect_uri: this.callBackUri,
  code: code,
  grant_type: 'authorization_code'
};
this.http.setDataSerializer('urlencoded');
this.http
  .post(this.CodeExchangeUrl, data, {})
  .then(a => {
    const infoToken: OneDriveTokenAskResponse = JSON.parse(a.data);
});

 

À des fins de maintenabilité, j'ai représenté la réponse de l'API sous la forme d'une interface OneDriveTokenAskResponse :

export interface OneDriveTokenAskResponse {
  token_type: string;
  expires_in: number;
  scope: string;
  access_token: string;
  refresh_token: string;
}

 

Dans un prochain article, nous verrons comment utiliser ce code pour effectuer des appels à l'API Microsoft Graph et notamment télécharger un fichier.

 

Pour rappel, cette article fait partie d'une série :

Happy coding :)

 

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus