Angular & Microsoft Graph [Part 2] : Identification

Récupération du token oAuth2

L'identification repose sur oAuth2. C'est-à-dire que Microsoft va vous générer un token que vous devrez envoyer aux apis d'Office365 pour gérer la sécurité.

NB: Nous allons faire ici tout cela "à la main", c'est-à-dire que nous n'allons pas utiliser des librairies proposées par Microsoft ou par des éditeurs/développeurs tiers pour bien comprendre comment cela fonctionne. Dans les exemples fournis par Microsoft, ils utilisent la librairie hellojs qui est compatible avec d'autres services que MS Graph comme Google, Facebook & co.

Pour cela, Microsoft met à notre disposition une page Web hébergée sur leur serveur dont l'URL est du type https://login.microsoftonline.com/common/oauth2/v2.0/authorize avec plein d'autres paramètres. Nous allons stocker ces paramètres dans le fichier environment.ts de notre application angular :

appId est le Guid généré sur le site de paramétrage de votre application Office365 (cf article précédent).

Dans le fichier environment.prod.ts, le contenu est identique sauf que l'URL du callback est l'URL définitive de notre site en production (https://www.myTjm/callback).

Quand on appelle la page de login de Microsoft avec les bons paramètres, l'utilisateur est invité à entrer ses identifiants. Quand il clique sur Login, Microsoft vérifie que ses identifiants sont les bons, puis redirige le navigateur vers votre page de callback avec dans l'URL, le token de sécurité oAuth que nous allons devoir sauvegarder pour les appels suivants.

J'ai donc créer un service, AuthService, qui contient une méthode login :

public login() {
    const url = this.getUrl();
    const width = 525,
      height = 525,
      screenX = window.screenX,
      screenY = window.screenY,
      outerWidth = window.outerWidth,
      outerHeight = window.outerHeight;

    const left = screenX + Math.max(outerWidth - width, 0) / 2;
    const top = screenY + Math.max(outerHeight - height, 0) / 2;

    const features = [
      'width=' + width,
      'height=' + height,
      'top=' + top,
      'left=' + left,
      'status=no',
      'resizable=yes',
      'toolbar=no',
      'menubar=no',
      'scrollbars=yes'];
    const popup = window.open(url, 'oauth', features.join(','));
    if (!popup) {
      alert('failed to pop up auth window');
    }
  }

Et la méthode getUrl() :

private getUrl(): string {
    let url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    url += '?client_id=' + environment.appId;
    url += '&response_type=token';
    url += '&redirect_uri=' + encodeURIComponent(environment.callback);
    url += '&scope=' + encodeURIComponent(environment.scope);
    return url;
  }

Vous voyez que dans les paramètres de l'URL appelée, on passe l'appId, le type de réponse attendu (token), l'URL de redirection (votre page callback) et les autorisations demandées (scope).

Donc dans notre application, il suffit d'appeler cette méthode login qui fera apparaitre dans une fenêtre popup le site d'identification de Microsoft. Quand l'utilisateur se sera identifié, Microsoft redirigera cette fenêtre popup vers notre page callback.

On va donc créer un composant callback dans notre application dont le code ne fera qu'appeler la méthode onCallback de notre service authService :

import { Component, OnInit } from '@angular/core';
import { AuthService } from '@services/msgraph';

@Component({
  selector: 'app-callback',
  template: ''
})
export class CallbackComponent implements OnInit {
  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.authService.onCallback();
  }
}

La seule chose que fait ce composant, c'est d'appeler le service qui va :

  • Récupérer le token de l'url et le sauvegarder dans le localstorage,
  • fermer le popup qu'il avait ouvert,
  • demander à la fenêtre parente du popup de se rafraichir.
  /** Appelée par le callbackComponent */
  public onCallback() {
    const token = this.getTokenFromURL();

    if (token) {
      this.saveAuthToken(token);
    }
    window.opener.location.reload();
    window.close();
  }

  private getTokenFromURL(): IMsGraphToken {
    if (window.location.hash) {
      const authResponse = window.location.hash.substring(1);
      const authInfo = JSON.parse(
        '{' + authResponse.replace(/([^=]+)=([^&]+)&?/g, '"$1":"$2",').slice(0, -1) + '}',
        (key, value) => key === '' ? value : decodeURIComponent(value));
      return authInfo;
    } else {
      alert('failed to receive auth token');
    }
  }

NB: Le code de la méthode getAuthToken analyse les paramètres de l'URL. J'avoue avoir piqué ce code sur l'un des exemples de Microsoft, mais je me souviens plus ou !!!

Une fois l'utilisateur authentifié, on a bien dans le localstorage le token (et des informations supplémentaires comme la date d'expiration) :

Interception des appels

Maintenant qu'on a bien le token d'identification, dès que l'on va vouloir appeler une api REST de Microsoft Graph, il faudra passer dans le header de la requête ce token. Ce token sera mis à disposition toujours par notre service AuthService avec la méthode getAuthToken qui renvoi une interface que j'ai définie IMsGraphToken :

  /** Retourne le token d'identification du user dans Graph */
  public getAuthToken(): IMsGraphToken {
    return <imsgraphtoken>JSON.parse(localStorage.getItem(this.tokenKey)); }

export interface IMsGraphToken {
    access_token: string;
    expires_in: number;
    scope: string;
    session_state: string;
}

Nous allons donc créer un intercepteur angular "classique" :

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService, GraphUrl } from '@services/msgraph';

@Injectable({
  providedIn: 'root'
})
export class AuthInterceptorService implements HttpInterceptor {

  constructor(private authService: AuthService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    const token = this.authService.getAuthToken();
    if (token == null) { return next.handle(req); }
    if (token.access_token == null) { return next.handle(req); }

    // ne change le header que si l'URL commence par https://graph.microsoft.com/v1.0/ définie dans la constante GraphUrl
    if (!req.url.startsWith(GraphUrl)) {
      return next.handle(req);
    }

    const authReq = req.clone({
      headers:
        req.headers
          .set('Authorization', 'Bearer ' + token.access_token)
          .set('Content-Type', 'application/json')
    });

    return next.handle(authReq);
  }
}

N'oublions pas de l'ajouter dans le providers de notre app.module.ts :

  providers: [
    { provide: LOCALE_ID, useValue: 'fr' },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptorService,
      multi: true
    },
  ],

Et voila, dès que l'on dera une requête Http vers MS Graph, notre token sera ajouté et l'on sera authentifié.

blog comments powered by Disqus