Écrire une application PHP pour obtenir la messagerie, le calendrier et les contacts OutlookWrite a PHP app to get Outlook mail, calendar, and contacts

L’objectif de ce guide est de passer en revue le processus de création d’une application PHP simple qui récupère des messages dans Office 365 ou Outlook.com. Le code source dans ce référentiel correspond à ce que vous devez obtenir si vous suivez les étapes de la présente procédure.The purpose of this guide is to walk through the process of creating a simple PHP app that retrieves messages in Office 365 or Outlook.com. The source code in this repository is what you should end up with if you follow the steps outlined here.

Ce guide utilise Microsoft Graph pour accéder à la messagerie Outlook. Microsoft recommande d’utiliser Microsoft Graph pour accéder à la messagerie, au calendrier et aux contacts Outlook. Vous devez utiliser les API Outlook directement (via https://outlook.office.com/api) uniquement si vous avez besoin d’une fonctionnalité qui n’est pas disponible sur les points de terminaison de Graph. Pour obtenir une version de cet exemple qui utilise les API Outlook, reportez-vous à cette branche.This guide will use Microsoft Graph to access Outlook mail. Microsoft recommends using Microsoft Graph to access Outlook mail, calendar, and contacts. You should use the Outlook APIs directly (via https://outlook.office.com/api) only if you require a feature that is not available on the Graph endpoints. For a version of this sample that uses the Outlook APIs, see this branch.

Ce guide suppose que PHP est déjà installé sur votre ordinateur et que vous utilisez votre ordinateur de développement. Le didacticiel a été créé à l’aide de PHP version 5.6.30.This guide assumes that you already have PHP installed and working on your development machine. The tutorial was created using PHP 5.6.30.

Création de l’applicationCreate the app

Passons directement aux choses sérieuses ! Pour commencer, nous allons utiliser PHP 7.1, Composer et Laravel pour créer rapidement notre application PHP. Si vous ne possédez pas ces outils, installez-les avant de continuer.Let's dive right in! To start, we're going to use PHP 7.1, Composer and Laravel to quickly create our PHP app. If you don't already have these tools installed, please install them before proceeding.

Sur votre ordinateur de développement, ouvrez votre invite de commandes ou environnement de ligne de commande dans le répertoire où vous souhaitez créer le projet. Pour créer le projet, entrez la commande suivante.On your development machine, open your command prompt or shell to a directory where you want to create your new project. Enter the following command to create the project.

laravel new php-tutorial

Cette opération crée un répertoire php-tutorial, établit la structure du projet et télécharge les dépendances. Une fois la commande terminée, nous allons nous assurer que tout fonctionne. Modifiez votre répertoire actuel par php-tutorial et exécutez la commande suivante pour démarrer le serveur de développement Laravel.This will create new php-tutorial directory, scaffold the project, and download dependencies. Once the command completes, let's make sure that everything is working. Change your current directory to the php-tutorial directory, and run the following command to start the Laravel development server.

php artisan serve

Ouvrez votre navigateur et accédez à http://localhost:8000. Vous devez voir la page de démarrage Laravel par défaut.Open your brower and browse to http://localhost:8000. You should see a default Laravel start page.

Conception de l’applicationDesigning the app

Notre application sera très simple. Lorsqu’un utilisateur visite le site, il voit un lien pour se connecter et afficher ses messages électroniques. Le fait de cliquer sur ce lien conduit à la page de connexion Azure où il peut se connecter avec son compte Office 365 ou Outlook.com et obtenir l’accès à notre application. Enfin, il est redirigé vers notre application, qui affiche la liste des messages les plus récents dans la boîte de réception de l’utilisateur.Our app will be very simple. When a user visits the site, they will see a link to log in and view their email. Clicking that link will take them to the Azure login page where they can login with their Office 365 or Outlook.com account and grant access to our app. Finally, they will be redirected back to our app, which will display a list of the most recent email in the user's inbox.

Commençons par créer une mise en page. Créez un fichier intitulé layout.blade.php dans le répertoire ./php-tutorial/resources/views et ajoutez le code suivant.Let's begin by creating a layout. Create a new file named layout.blade.php in the ./php-tutorial/resources/views directory and add the following code.

Contenu du fichier ./php-tutorial/resources/views/layout.blade.phpContents of the ./php-tutorial/resources/views/layout.blade.php file

<!DOCTYPE html>
<html>
  <head>
    <title>PHP Outlook Sample</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link rel="stylesheet" href="{{ asset('/css/app.css') }}">
  </head>
  <body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">PHP Outlook Sample</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
            <li class="<?php echo ($_SERVER['REQUEST_URI'] == '/' ? 'active' : '');?>"><a href="/">Home</a></li>
            <li class="<?php echo ($_SERVER['REQUEST_URI'] == '/mail' ? 'active' : '');?>"><a href="/mail">Inbox</a></li>
          </ul>
          <?php if(isset($username)) { ?>
          <ul class="nav navbar-nav navbar-right">
            <li><p class="navbar-text">Hello <?php echo $username ?>!</p></li>
          </ul>
          <?php } ?>
        </div><!--/.nav-collapse -->
      </div>
    </nav>

    <div class="container" role="main">
      @yield('content')
    </div>

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  </body>
</html>

Cela nous permet d’obtenir une structure HTML de base et ajoute Bootstrap pour le style.This just gives us a basic HTML structure and adds Bootstrap for styling.

Maintenant, nous allons supprimer le code CSS par défaut de ./php-tutorial/public/css/app.css et ajouter quelques éléments de style de base afin d’utiliser uniquement les valeurs par défaut de Bootstrap. Ouvrez le fichier et tout son contenu avec l’élément suivant.Now let's remove the default CSS code from ./php-tutorial/public/css/app.css and add some basic styling so we're just using Bootstrap's defaults. Open the file and it's entire contents with the following.

Contenu du fichier ./php-tutorial/public/css/app.cssContents of the ./php-tutorial/public/css/app.css file

body {
  padding-top: 70px;
  padding-bottom: 30px;
}

Maintenant, nous allons modifier la page d’accueil existante pour utiliser cette mise en page. Ouvrez le fichier ./php-tutorial/resources/views/welcome.blade.php et remplacez son contenu par le code suivant.Now we'll modify the existing home page to use this layout. Open the ./php-tutorial/resources/views/welcome.blade.php file and replace it's contents with the following code.

Contenu du fichier ./php-tutorial/resources/views/welcome.blade.phpContents of the ./php-tutorial/resources/views/welcome.blade.php file

@extends('layout')

@section('content')
<div class="jumbotron">
  <h1>PHP Outlook Sample</h1>
  <p>This example shows how to get an OAuth token from Azure using the <a href="https://docs.microsoft.com/azure/active-directory/develop/active-directory-v2-protocols-oauth-code" target="_blank">authorization code grant flow</a> and to use that token to make calls to the Outlook APIs in the <a href="https://docs.microsoft.com/graph/overview" target="_blank">Microsoft Graph</a>.</p>
  <p>
    <a class="btn btn-lg btn-primary" href="/signin" role="button" id="connect-button">Connect to Outlook</a>
  </p>
</div>
@endsection

Le bouton Se connecter à Outlook n’a aucun effet pour le moment, mais nous allons vite corriger cela.The Connect to Outlook button doesn't do anything yet, but we'll fix that soon.

Inscription de l’applicationRegister the app

Important

Configuration requise de compteAccount requirements

Pour utiliser le Centre d’administration Azure Active Directory, vous avez besoin d’un compte professionnel ou scolaire Office 365, ou d’un compte Microsoft.In order to use the Azure Active Directory admin center, you need either an Office 365 work or school account, or a Microsoft account. Si vous n’avez pas un de ces comptes, les options suivantes sont possibles :If you don't have either of these, you have a number of options:

  • Vous inscrire à un nouveau compte Microsoft ici.Sign up for a new Microsoft account here.
  • Vous pouvez obtenir un abonnement Office 365 de deux manières différentes :You can obtain an Office 365 subscription in a couple of different ways:
  1. Ouvrez un navigateur et accédez au Centre d’administration Azure Active Directory.Open a browser and navigate to the Azure Active Directory admin center. Connectez-vous à l’aide d’un compte personnel (compte Microsoft) ou d’un compte professionnel ou scolaire.Login using a personal account (aka: Microsoft Account) or Work or School Account.

  2. Sélectionnez Azure Active Directory dans le volet de navigation gauche, puis sélectionnez Inscriptions d’applications (préversion) sous Gérer.Select Azure Active Directory in the left-hand navigation, then select App registrations (Preview) under Manage.

  3. Sélectionnez Nouvelle inscription.Select New registration. Sur la page Inscrire une application, définissez les valeurs comme suit.On the Register an application page, set the values as follows.

    • Définissez le Nom sur PHP Outlook Tutorial.Set Name to PHP Outlook Tutorial.
    • Définissez les Types de comptes pris en charge sur Comptes dans un annuaire organisationnel et comptes personnels Microsoft.Set Supported account types to Accounts in any organizational directory and personal Microsoft accounts.
    • Sous URI de redirection, définissez la première flèche déroulante sur Web, et la valeur sur http://localhost:8000/authorize.Under Redirect URI, set the first drop-down to Web and set the value to http://localhost:8000/authorize.
  4. Choisissez Inscrire.Choose Register. Sur la page Didacticiel Outlook PHP, copiez la valeur de l’ID d’application (client) et enregistrez-la car vous en aurez besoin à l’étape suivante.On the PHP Outlook Tutorial page, copy the value of the Application (client) ID and save it, you will need it in the next step.

  5. Sélectionnez Authentification sous Gérer.Select Authentication under Manage. Recherchez la section Octroi implicite, puis activez Jetons d’ID.Locate the Implicit grant section and enable ID tokens. Choisissez Enregistrer.Choose Save.

  6. Sélectionnez Certificats et secrets sous Gérer.Select Certificates & secrets under Manage. Sélectionnez le bouton Nouveau secret client.Select the New client secret button. Entrez une valeur dans Description, sélectionnez une des options pour Expire le, puis choisissez Ajouter.Enter a value in Description and select one of the options for Expires and choose Add.

  7. Copiez la valeur du secret client avant de quitter cette page.Copy the client secret value before you leave this page. Vous en aurez besoin à l’étape suivante.You will need it in the next step.

    Important

    Ce secret client n’apparaîtra plus jamais, aussi veillez à le copier maintenant.This client secret is never shown again, so make sure you copy it now.

Implémentation d’OAuth2Implementing OAuth2

Dans cette section, notre objectif consiste à faire que le bouton sur la page d’accueil initie le flux d’octroi de code d’autorisation OAuth2 avec Azure AD.Our goal in this section is to make the button on our home page initiate the OAuth2 Authorization Code Grant flow with Azure AD.

Nous allons utiliser le client League OAuth 2 pour gérer nos demandes de jeton et d’autorisation. Nous allons l’installer avant de continuer.We'll use the League OAuth 2 Client to handle our authorization and token requests. Let's install that before proceeding.

Ouvrez le fichier ./php-tutorial/composer.json et recherchez l’entrée require. Mettez à jour cette entrée pour ajouter "league/oauth2-client": "^2.0" et enregistrez le fichier.Open the ./php-tutorial/composer.json file and locate the require entry. Update this entry to add "league/oauth2-client": "^2.0" and save the file.

Entrée require mise à jour dans ./php-tutorial/composer.jsonUpdated require entry in ./php-tutorial/composer.json

"require": {
    "php": ">=5.6.4",
    "laravel/framework": "5.4.*",
    "laravel/tinker": "~1.0",
    "league/oauth2-client": "^2.0"
},

Exécutez la commande suivante à partir de votre invite de commandes/environnement de ligne de commande dans le répertoire php-tutorial.Run the following command from your command prompt/shell in the php-tutorial directory.

composer update

Ceci permet d’installer le client OAuth 2 et les dépendances dans votre dossier ./php-tutorial/vendors.This will install the OAuth 2 client and dependencies into your ./php-tutorial/vendors folder.

Une fois que Composer a téléchargé les bibliothèques requises, nous allons créer un contrôleur pour contenir toutes nos fonctions OAuth. Créez un fichier dans le répertoire ./php-tutorial/app/Http/Controllers appelé AuthController.php. Ajoutez le code suivant.Once composer is done downloading the required libraries, let's create a new controller to contain all of our OAuth functions. Create a new file in the ./php-tutorial/app/Http/Controllers directory called AuthController.php. Add the following code.

Contenu du fichier ./php-tutorial/app/Http/Controllers/AuthController.phpContents of the ./php-tutorial/app/Http/Controllers/AuthController.php file

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class AuthController extends Controller
{
  public function signin()
  {
    if (session_status() == PHP_SESSION_NONE) {
      session_start();
    }

    // Initialize the OAuth client
    $oauthClient = new \League\OAuth2\Client\Provider\GenericProvider([
      'clientId'                => env('OAUTH_APP_ID'),
      'clientSecret'            => env('OAUTH_APP_PASSWORD'),
      'redirectUri'             => env('OAUTH_REDIRECT_URI'),
      'urlAuthorize'            => env('OAUTH_AUTHORITY').env('OAUTH_AUTHORIZE_ENDPOINT'),
      'urlAccessToken'          => env('OAUTH_AUTHORITY').env('OAUTH_TOKEN_ENDPOINT'),
      'urlResourceOwnerDetails' => '',
      'scopes'                  => env('OAUTH_SCOPES')
    ]);

    // Output the authorization endpoint
    echo 'Auth URL: '.$oauthClient->getAuthorizationUrl();
    exit();
  }
}

Nous devons maintenant configurer un itinéraire vers notre nouvelle méthode de contrôleur. Ouvrez le fichier ./php-tutorial/routes/web.php et ajoutez la ligne suivante en bas du fichier.Now we need to configure a route to our new controller method. Open the ./php-tutorial/routes/web.php file and add the following line at the bottom of the file.

Nouvelle entrée dans ./php-tutorial/routes/web.phpNew entry in ./php-tutorial/routes/web.php

Route::get('/signin', 'AuthController@signin');

Maintenant, nous allons configurer l’application. Nous allons utiliser des variables d’environnement pour conserver tous les détails OAuth. Ouvrez le fichier ./php-tutorial/.env et ajoutez les lignes suivantes dans la partie inférieure.Now let's configure the app. We'll use environment variables to hold all of the OAuth details. Open the ./php-tutorial/.env file and add the following lines at the bottom.

Nouvelles entrées dans ./php-tutorial/.envNew entries in ./php-tutorial/.env

OAUTH_APP_ID=YOUR_APP_ID_HERE
OAUTH_APP_PASSWORD=YOUR_APP_PASSWORD_HERE
OAUTH_REDIRECT_URI=http://localhost:8000/authorize
OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read'
OAUTH_AUTHORITY=https://login.microsoftonline.com/common
OAUTH_AUTHORIZE_ENDPOINT=/oauth2/v2.0/authorize
OAUTH_TOKEN_ENDPOINT=/oauth2/v2.0/token

Remplacez YOUR_APP_ID_HERE et YOUR_APP_PASSWORD_HERE par l’ID de l’application et le mot de passe que vous avez obtenus dans l’outil d’inscription d’application, puis enregistrez le fichier.Replace YOUR_APP_ID_HERE and YOUR_APP_PASSWORD_HERE with the application ID and password you obtained from the App Registration Tool and save the file.

Enregistrez toutes vos modifications et redémarrez le serveur de développement Laravel (php artisan serve). Accédez à http://localhost:8000 et cliquez sur le bouton Se connecter à Outlook. Vous devez voir l’URL d’autorisation indiqué sur la page, qui doit ressembler à ce qui suit.Save all your changes and restart the Laravel development server (php artisan serve). Browse to http://localhost:8000 and click on the Connect to Outlook button. You should see the authorization URL printed to the page, which should look something like this.

https://login.microsoftonline.com/common/oauth2/v2.0/authorize?state=48b0ad0b3b443a543cf823f98db760c4&scope=openid%20profile%20offline_access%20User.Read%20Mail.Read&response_type=code&approval_prompt=auto&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Foauth&client_id=<YOUR_CLIENT_ID>

Maintenant, nous allons mettre à jour l’application pour pouvoir réellement accéder à cette URL, afin que l’utilisateur puisse se connecter et octroyer l’accès à l’application. Nous allons également ajouter une autre méthode de contrôleur pour recevoir la redirection à partir du point de terminaison d’autorisation Azure. Tout d’abord, remplacez la méthode signin existante dans ./php-tutorial/app/Http/Controllers/AuthController.php par celle-ci.Now let's update the app to actually navigate to that URL so the user can sign in and grant access to the app. We'll also add another controller method to receive the redirect back from the Azure authorization endpoint. First, replace the existing signin method in ./php-tutorial/app/Http/Controllers/AuthController.php with this one.

public function signin()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  // Initialize the OAuth client
  $oauthClient = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => env('OAUTH_APP_ID'),
    'clientSecret'            => env('OAUTH_APP_PASSWORD'),
    'redirectUri'             => env('OAUTH_REDIRECT_URI'),
    'urlAuthorize'            => env('OAUTH_AUTHORITY').env('OAUTH_AUTHORIZE_ENDPOINT'),
    'urlAccessToken'          => env('OAUTH_AUTHORITY').env('OAUTH_TOKEN_ENDPOINT'),
    'urlResourceOwnerDetails' => '',
    'scopes'                  => env('OAUTH_SCOPES')
  ]);

  // Generate the auth URL
  $authorizationUrl = $oauthClient->getAuthorizationUrl();

  // Save client state so we can validate in response
  $_SESSION['oauth_state'] = $oauthClient->getState();

  // Redirect to authorization endpoint
  header('Location: '.$authorizationUrl);
  exit();
}

Au lieu d’imprimer l’URL dans la réponse, elle enregistre l’état du client dans la session et utilise l’en-tête Location pour rediriger le navigateur.Instead of printing the URL to the response, it saves the client state to the session and uses the Location header to redirect the browser.

Maintenant, nous allons ajouter une méthode authorize pour recevoir la redirection d’Azure et extraire le code d’autorisation de la réponse. Ajoutez la méthode suivante dans ./php-tutorial/app/Http/Controllers/AuthController.php.Now, let's add an authorize method to receive the redirect back from Azure and extract the authorization code from the response. Add the following method in ./php-tutorial/app/Http/Controllers/AuthController.php.

Nouvelle méthode gettoken dans ./php-tutorial/app/Http/Controllers/AuthController.phpNew gettoken method in ./php-tutorial/app/Http/Controllers/AuthController.php

public function gettoken()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  // Authorization code should be in the "code" query param
  if (isset($_GET['code'])) {
    echo 'Auth code: '.$_GET['code'];
    exit();
  }
  elseif (isset($_GET['error'])) {
    exit('ERROR: '.$_GET['error'].' - '.$_GET['error_description']);
  }
}

Ajoutez un nouvel itinéraire dans ./php-tutorial/routes/web.phpAdd a new route in ./php-tutorial/routes/web.php

Nouvelle entrée dans ./php-tutorial/routes/web.phpNew entry in ./php-tutorial/routes/web.php

Route::get('/authorize', 'AuthController@gettoken');

Enregistrez vos modifications et accédez à http://localhost:8000. Cette fois, lorsque vous cliquez sur le bouton Se connecter à Outlook, vous devez être redirigé vers la page de connexion. Si vous entrez vos informations de compte Office 365 ou Outlook.com et que vous acceptez les autorisations requises, l’application affiche le code d’autorisation. Nous allons l’utiliser.Save your changes and browse to http://localhost:8000. This time when you click on the Connect to Outlook button you should be redirected to the login page. If you enter your Office 365 or Outlook.com account information and accept the requested permissions, the app will display the authorization code. Now let's do something with it.

Échange du code avec un jetonExchanging the code for a token

Nous allons mettre à jour la méthode gettoken pour utiliser le client OAuth 2 afin d’effectuer une demande de jeton. Nous allons tout d’abord vérifier que l’état inclus dans la redirection correspond à celui que nous avons enregistré dans la session après avoir généré l’URL d’autorisation, puis nous demanderons le jeton réel à l’aide du code d’autorisation. Remplacez l’élément gettoken existant par ce nouveau.Let's update the gettoken method to use the OAuth 2 client to make a token request. First we'll verify that the state included in the redirect matches the one we saved in the session after generating the authorization URL, then we'll make the actual token request using the authorization code. Replace the existing gettoken with this new one.

Fonction gettoken mise à jour dans ./php-tutorial/app/Http/Controllers/AuthController.phpUpdated gettoken function in ./php-tutorial/app/Http/Controllers/AuthController.php

public function gettoken()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  // Authorization code should be in the "code" query param
  if (isset($_GET['code'])) {
    // Check that state matches
    if (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth_state'])) {
      exit('State provided in redirect does not match expected value.');
    }

    // Clear saved state
    unset($_SESSION['oauth_state']);

    // Initialize the OAuth client
    $oauthClient = new \League\OAuth2\Client\Provider\GenericProvider([
      'clientId'                => env('OAUTH_APP_ID'),
      'clientSecret'            => env('OAUTH_APP_PASSWORD'),
      'redirectUri'             => env('OAUTH_REDIRECT_URI'),
      'urlAuthorize'            => env('OAUTH_AUTHORITY').env('OAUTH_AUTHORIZE_ENDPOINT'),
      'urlAccessToken'          => env('OAUTH_AUTHORITY').env('OAUTH_TOKEN_ENDPOINT'),
      'urlResourceOwnerDetails' => '',
      'scopes'                  => env('OAUTH_SCOPES')
    ]);

    try {
      // Make the token request
      $accessToken = $oauthClient->getAccessToken('authorization_code', [
        'code' => $_GET['code']
      ]);

      echo 'Access token: '.$accessToken->getToken();
    }
    catch (League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
      exit('ERROR getting tokens: '.$e->getMessage());
    }
    exit();
  }
  elseif (isset($_GET['error'])) {
    exit('ERROR: '.$_GET['error'].' - '.$_GET['error_description']);
  }
}

Enregistrez vos modifications et réalisez à nouveau le processus de connexion dans votre application. Cette fois, vous devez voir le jeton d’accès affiché.Save your changes and go through the login process in your app again. This time you should see the access token displayed.

Actualisation du jeton d’accèsRefreshing the access token

Les jetons d’accès renvoyés par Azure sont valides pendant une heure. Si vous utilisez le jeton après son expiration, les appels d’API renvoient des erreurs 401. Vous pouvez demander à l’utilisateur de se connecter à nouveau, mais la meilleure solution consiste à actualiser le jeton silencieusement. Nous allons donc stocker les jetons et les actualiser si nécessaire avant de poursuivre.Access tokens returned from Azure are valid for an hour. If you use the token after it has expired, the API calls will return 401 errors. You could ask the user to sign in again, but the better option is to refresh the token silently. So let's work on storing the tokens and refreshing them if needed before we proceed.

Tout d’abord, nous allons créer une classe pour gérer le stockage et l’extraction des jetons. Dans la mesure où il s’agit d’un exemple, nous allons procéder simplement et stocker tout le contenu dans la session. Pour les scénarios de production, il est préférable de stocker le jeton d’actualisation dans une base de données sécurisée.First, let's create a new class to manage storing and retrieving the token. Since this is a sample, we'll keep it simple and store everything in the session. For production scenarios, it would be way better to store the refresh token in a secure database.

Créez un dossier dans le répertoire ./php-tutorial/app appelé TokenStore. Dans ce nouveau dossier, créez un fichier appelé TokenCache.php et ajoutez le code suivant.Create a new folder in the ./php-tutorial/app directory called TokenStore. In this new folder, create a file called TokenCache.php and add the following code.

Contenu du fichier ./php-tutorial/app/TokenStore/TokenCache.phpContents of the ./php-tutorial/app/TokenStore/TokenCache.php file

<?php

namespace App\TokenStore;

class TokenCache {
  public function storeTokens($access_token, $refresh_token, $expires) {
    $_SESSION['access_token'] = $access_token;
    $_SESSION['refresh_token'] = $refresh_token;
    $_SESSION['token_expires'] = $expires;
  }

  public function clearTokens() {
    unset($_SESSION['access_token']);
    unset($_SESSION['refresh_token']);
    unset($_SESSION['token_expires']);
  }

  public function getAccessToken() {
    // Check if tokens exist
    if (empty($_SESSION['access_token']) ||
        empty($_SESSION['refresh_token']) ||
        empty($_SESSION['token_expires'])) {
      return '';
    }

    // Check if token is expired
    //Get current time + 5 minutes (to allow for time differences)
    $now = time() + 300;
    if ($_SESSION['token_expires'] <= $now) {
      // Token is expired (or very close to it)
      // so let's refresh

      // Initialize the OAuth client
      $oauthClient = new \League\OAuth2\Client\Provider\GenericProvider([
        'clientId'                => env('OAUTH_APP_ID'),
        'clientSecret'            => env('OAUTH_APP_PASSWORD'),
        'redirectUri'             => env('OAUTH_REDIRECT_URI'),
        'urlAuthorize'            => env('OAUTH_AUTHORITY').env('OAUTH_AUTHORIZE_ENDPOINT'),
        'urlAccessToken'          => env('OAUTH_AUTHORITY').env('OAUTH_TOKEN_ENDPOINT'),
        'urlResourceOwnerDetails' => '',
        'scopes'                  => env('OAUTH_SCOPES')
      ]);

      try {
        $newToken = $oauthClient->getAccessToken('refresh_token', [
          'refresh_token' => $_SESSION['refresh_token']
        ]);

        // Store the new values
        $this->   storeTokens($newToken->getToken(), $newToken->getRefreshToken(),
          $newToken->getExpires());

        return $newToken->getToken();
      }
      catch (League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
        return '';
      }
    }
    else {
      // Token is still valid, just return it
      return $_SESSION['access_token'];
    }
  }
}

Cette option ajoute trois nouvelles fonctions. La fonction storeTokens enregistre les valeurs dans la session. La fonction clearTokens les supprime. La fonction getAccessToken vérifie l’expiration du jeton d’accès actuel et l’actualise si nécessaire.This adds three new functions. The storeTokens function will save the values in the session. The clearTokens function will remove them. The getAccessToken function will check expiration of the current access token and refresh it if necessary.

Maintenant, nous allons mettre à jour la méthode gettoken pour utiliser cette nouvelle classe en vue de stocker les valeurs et rediriger vers /mail dans notre application. Remplacez l’élément gettoken existant par cette nouvelle version.Now let's update the gettoken method to use this new class to store the values and redirect to /mail in our app. Replace the existing gettoken with this new version.

Fonction gettoken mise à jour dans ./php-tutorial/app/Http/Controllers/AuthController.phpUpdated gettoken function in ./php-tutorial/app/Http/Controllers/AuthController.php

public function gettoken()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  // Authorization code should be in the "code" query param
  if (isset($_GET['code'])) {
    // Check that state matches
    if (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth_state'])) {
      exit('State provided in redirect does not match expected value.');
    }

    // Clear saved state
    unset($_SESSION['oauth_state']);

    // Initialize the OAuth client
    $oauthClient = new \League\OAuth2\Client\Provider\GenericProvider([
      'clientId'                => env('OAUTH_APP_ID'),
      'clientSecret'            => env('OAUTH_APP_PASSWORD'),
      'redirectUri'             => env('OAUTH_REDIRECT_URI'),
      'urlAuthorize'            => env('OAUTH_AUTHORITY').env('OAUTH_AUTHORIZE_ENDPOINT'),
      'urlAccessToken'          => env('OAUTH_AUTHORITY').env('OAUTH_TOKEN_ENDPOINT'),
      'urlResourceOwnerDetails' => '',
      'scopes'                  => env('OAUTH_SCOPES')
    ]);

    try {
      // Make the token request
      $accessToken = $oauthClient->getAccessToken('authorization_code', [
        'code' => $_GET['code']
      ]);

      // Save the access token and refresh tokens in session
      // This is for demo purposes only. A better method would
      // be to store the refresh token in a secured database
      $tokenCache = new \App\TokenStore\TokenCache;
      $tokenCache->storeTokens($accessToken->getToken(), $accessToken->getRefreshToken(),
        $accessToken->getExpires());

      // Redirect back to mail page
      return redirect()->route('mail');
    }
    catch (League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
      exit('ERROR getting tokens: '.$e->getMessage());
    }
    exit();
  }
  elseif (isset($_GET['error'])) {
    exit('ERROR: '.$_GET['error'].' - '.$_GET['error_description']);
  }
}

Avant de continuer, nous allons implémenter un itinéraire /mail simple pour tester notre classe TokenCache. D’abord, créez un contrôleur pour notre vue de messagerie. Créez un fichier dans le répertoire ./php-tutorial/app/Http/Controllers appelé OutlookController.php. Ajoutez le code suivant.Before we move on, let's implement a very simple /mail route to test our TokenCache class. First, create a new controller for our mail view. Create a new file in the ./php-tutorial/app/Http/Controllers directory called OutlookController.php. Add the following code.

Contenu du fichier ./php-tutorial/app/Http/Controllers/OutlookController.phpContents of the ./php-tutorial/app/Http/Controllers/OutlookController.php file

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class OutlookController extends Controller
{
  public function mail()
  {
    if (session_status() == PHP_SESSION_NONE) {
      session_start();
    }

    $tokenCache = new \App\TokenStore\TokenCache;

    echo 'Token: '.$tokenCache->getAccessToken();
  }
}

Ouvrez le fichier ./php-tutorial/routes/web.php et ajoutez la ligne suivante en bas du fichier.Open the ./php-tutorial/routes/web.php file and add the following line at the bottom of the file.

Nouvelle entrée dans ./php-tutorial/routes/web.phpNew entry in ./php-tutorial/routes/web.php

Route::get('/mail', 'OutlookController@mail')->name('mail');

Enregistrez vos modifications, accédez à http://localhost:8000 et reconnectez-vous. Cette fois, l’application doit rediriger vers https://localhost:8000/mail et afficher le jeton, tout en vérifiant que notre cache de jeton fonctionne au niveau des contrôleurs. Maintenant que nous avons un jeton d’accès et que nous pouvons l’actualiser le cas échéant, nous pouvons utiliser l’API de messagerie.Save your changes, browse to http://localhost:8000 and sign in again. This time, the app should redirect to https://localhost:8000/mail and display the token, verifying that our token cache is working across controllers. Now that we have an access token and we can refresh if needed, we're ready to use the Mail API.

Utilisation de l’API de messagerieUsing the Mail API

Nous allons utiliser le kit de développement logiciel (SDK) Microsoft Graph pour PHP pour émettre tous nos appels d’API Outlook. Nous allons donc commencer par l’installer.We're going to use the Microsoft Graph SDK for PHP to make all of our Outlook API Calls, so let's start by installing it.

Ouvrez le fichier ./php-tutorial/composer.json et recherchez l’entrée require. Mettez à jour cette entrée pour ajouter "microsoft/microsoft-graph": "1.0.*" et enregistrez le fichier.Open the ./php-tutorial/composer.json file and locate the require entry. Update this entry to add "microsoft/microsoft-graph": "1.0.*" and save the file.

Entrée require mise à jour dans ./php-tutorial/composer.jsonUpdated require entry in ./php-tutorial/composer.json

"require": {
    "php": ">=5.6.4",
    "laravel/framework": "5.4.*",
    "laravel/tinker": "~1.0",
    "league/oauth2-client": "^2.0",
    "microsoft/microsoft-graph": "1.0.*"
},

Exécutez la commande suivante à partir de votre invite de commandes/environnement de ligne de commande dans le répertoire php-tutorial.Run the following command from your command prompt/shell in the php-tutorial directory.

composer update

Maintenant, nous allons modifier la fonction mail.Now let's modify the mail function. Pour notre première utilisation du Kit de développement logiciel (SDK) Graph, nous allons tâcher d’obtenir le nom de l’utilisateur, juste pour vérifier que nous pouvons appeler l’API Microsoft Graph.Our first use of the Graph SDK here will be to get the user's name, just to verify that we can call the Microsoft Graph API.

Ajoutez les lignes suivantes après la ligne use App\Http\Controllers\Controller; dans OutlookController.php :Add the following lines just after the use App\Http\Controllers\Controller; line in OutlookController.php.

use Microsoft\Graph\Graph;
use Microsoft\Graph\Model;

Remplacez la fonction mail existante par le code suivant.Replace the existing mail function with the following code.

Fonction mail mise à jour dans ./php-tutorial/app/Http/Controllers/OutlookController.phpUpdated mail function in ./php-tutorial/app/Http/Controllers/OutlookController.php

public function mail()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  $tokenCache = new \App\TokenStore\TokenCache;

  $graph = new Graph();
  $graph->setAccessToken($tokenCache->getAccessToken());

  $user = $graph->createRequest('GET', '/me')
                ->setReturnType(Model\User::class)
                ->execute();

  echo 'User: '.$user->getDisplayName();
}

Enregistrez vos modifications et actualisez la vue de messagerie.Save your changes and refresh the mail view. Vous devez voir le nom de l’utilisateur authentifié.You should see the authenticated user's name. (Si ce n’est pas le cas, recommencez au niveau de l’URL http://localhost:8000 et reconnectez-vous.)(If not, start over at http://localhost:8000 and login again.)

Maintenant, nous allons ajouter du code pour récupérer les messages de l’utilisateur. Remplacez la fonction mail existante par ce qui suit.Now let's add code to retrieve the user's messages. Replace the existing mail function with the following.

Fonction mail mise à jour dans ./php-tutorial/app/Http/Controllers/OutlookController.phpUpdated mail function in ./php-tutorial/app/Http/Controllers/OutlookController.php

public function mail()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  $tokenCache = new \App\TokenStore\TokenCache;

  $graph = new Graph();
  $graph->setAccessToken($tokenCache->getAccessToken());

  $user = $graph->createRequest('GET', '/me')
                ->setReturnType(Model\User::class)
                ->execute();

  echo 'User: '.$user->getDisplayName().'<br/>';

  $messageQueryParams = array (
    // Only return Subject, ReceivedDateTime, and From fields
    "\$select" => "subject,receivedDateTime,from",
    // Sort by ReceivedDateTime, newest first
    "\$orderby" => "receivedDateTime DESC",
    // Return at most 10 results
    "\$top" => "10"
  );

  $getMessagesUrl = '/me/mailfolders/inbox/messages?'.http_build_query($messageQueryParams);
  $messages = $graph->createRequest('GET', $getMessagesUrl)
                    ->setReturnType(Model\Message::class)
                    ->execute();

  foreach($messages as $msg) {
    echo 'Message: '.$msg->getSubject().'<br/>';
  }
}

Récapitulatif du nouveau code dans la fonction mail :To summarize the new code in the mail function:

  • Il crée un objet client Graph et l’initialise pour utiliser le jeton d’accès transmis à la fonction.It creates a Graph client object and initializes it to use the access token passed to the function.
  • Il appelle l’API /me/mailfolders/inbox/messages pour obtenir les messages de la boîte de réception et utilise d’autres méthodes pour contrôler la demande :It calls the /me/mailfolders/inbox/messages API to get inbox messages, and uses other methods to control the request:
    • Il utilise la méthode top avec la valeur 10 pour limiter les résultats aux 10 premiers.It uses the top method with a value of 10 to limit the results to the first 10.
    • Il utilise la méthode select uniquement pour demander les propriétés subject, from, receivedDateTime et isRead.It uses the select method to only request the subject, from, receivedDateTime, and isRead properties.
    • Il utilise la méthode orderby avec la valeur receivedDateTime desc pour obtenir les messages les plus récents en premier.It uses the orderby method with a value of receivedDateTime desc to get the newest messages first.
  • Il effectue une boucle sur les résultats et imprime l’objet.It loops over the results and prints out the subject.

Si vous redémarrez l’application maintenant, vous devez obtenir une liste très approximative du tableau de résultats. Nous allons ajouter un peu de code HTML et PHP pour afficher les résultats de façon plus conviviale.If you restart the app now, you should get a very rough listing of the results array. Let's add a little HTML and PHP to display the results in a nicer way.

Affichage des résultatsDisplaying the results

Nous allons ajouter une vue de base à notre application pour afficher les messages. Créez un fichier dans le dossier ./php-tutorial/resources/views appelé mail.blade.php et ajoutez le code suivant.We'll add a basic view to our app to render the messages. Create a new file in the ./php-tutorial/resources/views folder called mail.blade.php and add the following code.

Contenu du fichier ./php-tutorial/resources/views/mail.blade.phpContents of the ./php-tutorial/resources/views/mail.blade.php file

@extends('layout')

@section('content')
<div id="inbox" class="panel panel-default">
  <div class="panel-heading">
    <h1 class="panel-title">Inbox</h1>
  </div>
  <div class="panel-body">
    Here are the 10 most recent messages in your inbox.
  </div>
  <div class="list-group">
    <?php if (isset($messages)) {
      foreach($messages as $message) { ?>
    <div class="list-group-item">
      <h3 class="list-group-item-heading"><?php echo $message->getSubject() ?></h3>
      <h4 class="list-group-item-heading"><?php echo $message->getFrom()->getEmailAddress()->getName() ?></h4>
      <p class="list-group-item-heading text-muted"><em>Received: <?php echo $message->getReceivedDateTime()->format(DATE_RFC2822) ?></em></p>
    </div>
    <?php  }
    } ?>
  </div>
</div>
@endsection

Maintenant, mettez à jour la fonction mail pour renvoyer cette vue.Now update the mail function to return this view.

Fonction mail mise à jour dans ./php-tutorial/app/Http/Controllers/OutlookController.phpUpdated mail function in ./php-tutorial/app/Http/Controllers/OutlookController.php

public function mail()
{
  if (session_status() == PHP_SESSION_NONE) {
    session_start();
  }

  $tokenCache = new \App\TokenStore\TokenCache;

  $graph = new Graph();
  $graph->setAccessToken($tokenCache->getAccessToken());

  $user = $graph->createRequest('GET', '/me')
                ->setReturnType(Model\User::class)
                ->execute();

  $messageQueryParams = array (
    // Only return Subject, ReceivedDateTime, and From fields
    "\$select" => "subject,receivedDateTime,from",
    // Sort by ReceivedDateTime, newest first
    "\$orderby" => "receivedDateTime DESC",
    // Return at most 10 results
    "\$top" => "10"
  );

  $getMessagesUrl = '/me/mailfolders/inbox/messages?'.http_build_query($messageQueryParams);
  $messages = $graph->createRequest('GET', $getMessagesUrl)
                    ->setReturnType(Model\Message::class)
                    ->execute();

  return view('mail', array(
    'username' => $user->getDisplayName(),
    'messages' => $messages
  ));
}

Enregistrez vos modifications et actualisez la page de messagerie. Vous devez maintenant voir la liste des messages rendue de façon conviviale.Save your changes and refresh the mail page. You should now get a nicely rendered list of messages.

Application terminée affichant la boîte de réception de l’utilisateur.

Ajout d’API de calendrier et de contactsAdding Calendar and Contacts APIs

Maintenant que vous maîtrisez l’appel de l’API de messagerie Outlook, vous allez pouvoir appeler les API de calendrier et de contact très facilement.Now that you've mastered calling the Outlook Mail API, doing the same for Calendar and Contacts APIs is similar and easy.

Conseil

Si vous avez suivi le didacticiel, vous avez probablement un jeton d’accès enregistré dans votre cookie de session. Ce jeton n’est valable que pour l’étendue Mail.Read. Pour appeler l’API de calendrier ou de contacts, nous devons ajouter de nouvelles étendues. Veillez à redémarrer votre navigateur pour vous débarrasser des cookies de session afin de pouvoir démarrer le processus de connexion depuis le début pour obtenir un nouveau jeton d’accès.If you've followed along with the tutorial, you probably have an access token saved in your session cookie. That token will only be valid for the Mail.Read scope. In order to call the Calendar or Contacts API, we will need to add new scopes. Be sure to restart your browser to get rid of the session cookie so that you can start the login process from the beginning to get a new access token.

Pour l’API de calendrier :For Calendar API:

  1. Mettez à jour la valeur OAUTH_SCOPES dans .\php-tutorial\.env pour inclure l’étendue Calendars.Read.Update the OAUTH_SCOPES value in .\php-tutorial\.env to include the Calendars.Read scope.

    Notes

    Veillez à redémarrer le serveur de développement après la modification de ce fichier !Be sure to restart the development server after changing this file!

    OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read Calendars.Read'
    
  2. Ajoutez une nouvelle fonction calendar à la classe OutlookController dans ./php-tutorial/app/Http/Controllers/OutlookController.php.Add a new function calendar to the OutlookController class in ./php-tutorial/app/Http/Controllers/OutlookController.php.

    public function calendar()
    {
      if (session_status() == PHP_SESSION_NONE) {
        session_start();
      }
    
      $tokenCache = new \App\TokenStore\TokenCache;
    
      $graph = new Graph();
      $graph->setAccessToken($tokenCache->getAccessToken());
    
      $user = $graph->createRequest('GET', '/me')
                    ->setReturnType(Model\User::class)
                    ->execute();
    
      $eventsQueryParams = array (
        // // Only return Subject, Start, and End fields
        "\$select" => "subject,start,end",
        // Sort by Start, oldest first
        "\$orderby" => "Start/DateTime",
        // Return at most 10 results
        "\$top" => "10"
      );
    
      $getEventsUrl = '/me/events?'.http_build_query($eventsQueryParams);
      $events = $graph->createRequest('GET', $getEventsUrl)
                      ->setReturnType(Model\Event::class)
                      ->execute();
    
      return view('calendar', array(
        'username' => $user->getDisplayName(),
        'events' => $events
      ));
    }
    
  3. Ajoutez un itinéraire pour /calendar à ./php-tutorial/routes/web.php.Add a route for /calendar to ./php-tutorial/routes/web.php.

    Route::get('/calendar', 'OutlookController@calendar')->name('calendar');
    
  4. Ajoutez un nouveau fichier appelé calendar.blade.php dans ./php-tutorial/resources/views. Ajoutez le code suivant.Add a new file called calendar.blade.php in ./php-tutorial/resources/views. Add the following code.

    @extends('layout')
    
    @section('content')
    <div id="inbox" class="panel panel-default">
      <div class="panel-heading">
        <h1 class="panel-title">Calendar</h1>
      </div>
      <div class="panel-body">
        Here are the 10 oldest events in your calendar.
      </div>
      <div class="list-group">
        <?php if (isset($events)) {
          foreach($events as $event) { ?>
        <div class="list-group-item">
          <h3 class="list-group-item-heading"><?php echo $event->getSubject() ?></h3>
          <p class="list-group-item-heading text-muted">Start: <?php echo (new DateTime($event->getStart()->getDateTime()))->format(DATE_RFC2822) ?></p>
          <p class="list-group-item-heading text-muted">End: <?php echo (new DateTime($event->getEnd()->getDateTime()))->format(DATE_RFC822) ?></p>
        </div>
        <?php  }
        } ?>
      </div>
    </div>
    @endsection
    
  5. Ajoutez un élément de barre de navigation dans ./php-tutorial/resources/views/layout.blade.php juste après l’entrée pour Boîte de réception.Add a nav bar item in ./php-tutorial/resources/views/layout.blade.php just after the entry for Inbox.

    <li class="<?php echo ($_SERVER['REQUEST_URI'] == '/calendar' ? 'active' : '');?>"><a href="/calendar">Calendar</a></li>
    
  6. Redémarrez l’application. Une fois connecté, cliquez sur l’élément de calendrier dans la barre de navigation.Restart the app. After signing in, click the Calendar item on the nav bar.

Pour l’API de contacts :For Contacts API:

  1. Mettez à jour la valeur OAUTH_SCOPES dans .\php-tutorial\.env pour inclure l’étendue Contacts.Read.Update the OAUTH_SCOPES value in .\php-tutorial\.env to include the Contacts.Read scope.

    Notes

    Veillez à redémarrer le serveur de développement après la modification de ce fichier !Be sure to restart the development server after changing this file!

    OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read Contacts.Read'
    
  2. Ajoutez une nouvelle fonction contacts à la classe OutlookController dans ./php-tutorial/app/Http/Controllers/OutlookController.php.Add a new function contacts to the OutlookController class in ./php-tutorial/app/Http/Controllers/OutlookController.php.

    public function contacts()
    {
      if (session_status() == PHP_SESSION_NONE) {
        session_start();
      }
    
      $tokenCache = new \App\TokenStore\TokenCache;
    
      $graph = new Graph();
      $graph->setAccessToken($tokenCache->getAccessToken());
    
      $user = $graph->createRequest('GET', '/me')
                    ->setReturnType(Model\User::class)
                    ->execute();
    
      $contactsQueryParams = array (
        // // Only return givenName, surname, and emailAddresses fields
        "\$select" => "givenName,surname,emailAddresses",
        // Sort by given name
        "\$orderby" => "givenName ASC",
        // Return at most 10 results
        "\$top" => "10"
      );
    
      $getContactsUrl = '/me/contacts?'.http_build_query($contactsQueryParams);
      $contacts = $graph->createRequest('GET', $getContactsUrl)
                        ->setReturnType(Model\Contact::class)
                        ->execute();
    
      return view('contacts', array(
        'username' => $user->getDisplayName(),
        'contacts' => $contacts
      ));
    }
    
  3. Ajoutez un itinéraire pour /contacts à ./php-tutorial/routes/web.php.Add a route for /contacts to ./php-tutorial/routes/web.php.

    Route::get('/contacts', 'OutlookController@contacts')->name('contacts');
    
  4. Ajoutez un nouveau fichier appelé contacts.blade.php dans ./php-tutorial/resources/views. Ajoutez le code suivant.Add a new file called contacts.blade.php in ./php-tutorial/resources/views. Add the following code.

    @extends('layout')
    
    @section('content')
    <div id="inbox" class="panel panel-default">
      <div class="panel-heading">
        <h1 class="panel-title">Contacts</h1>
      </div>
      <div class="panel-body">
        Here are your first 10 contacts.
      </div>
      <div class="list-group">
        <?php if (isset($contacts)) {
          foreach($contacts as $contact) { ?>
        <div class="list-group-item">
          <h3 class="list-group-item-heading"><?php echo $contact->getGivenName().' '.$contact->getSurname() ?></h3>
          <p class="list-group-item-heading"><?php echo $contact->getEmailAddresses()[0]['address']?></p>
        </div>
        <?php  }
        } ?>
      </div>
    </div>
    @endsection
    
  5. Ajoutez un élément de barre de navigation dans ./php-tutorial/resources/views/layout.blade.php juste après l’entrée pour Boîte de réception.Add a nav bar item in ./php-tutorial/resources/views/layout.blade.php just after the entry for Inbox.

    <li class="<?php echo ($_SERVER['REQUEST_URI'] == '/contacts' ? 'active' : '');?>"><a href="/contacts">Contacts</a></li>
    
  6. Redémarrez l’application. Une fois connecté, cliquez sur l’élément de contacts dans la barre de navigation.Restart the app. After signing in, click the Contacts item on the nav bar.