Schreiben einer PHP-App zum Abrufen von Outlook-Mail, -Kalender und -Kontakten

In diesem Leitfaden werden Sie schrittweise durch den Prozess des Erstellens einer PHP-App zum Abrufen von Nachrichten in Office 365 oder Outlook.com geführt. Wenn Sie die hier beschriebenen Schritte ausführen, sollte der Quellcode in diesem Repository das Ergebnis sein.

In diesem Leitfaden wird Microsoft Graph zum Zugriff auf Outlook-Mail verwendet. Microsoft empfiehlt die Verwendung von Microsoft Graph für den Zugriff auf Outlook-Mail, -Kalender und -Kontakte. Verwenden Sie die Outlook-APIs nur dann direkt (über https://outlook.office.com/api), wenn Sie ein Feature benötigen, das in den Graph-Endpunkten nicht verfügbar ist. Eine Version dieses Beispiels mit Verwendung der Outlook-APIs finden Sie in dieser Verzweigung.

In diesem Leitfaden wird davon ausgegangen, dass Sie PHP bereits installiert haben und auf Ihrem Entwicklungscomputer ausführen. Das Lernprogramm wurde mit PHP 5.6.30 erstellt.

Erstellen der App

Lassen Sie uns direkt loslegen. Zu Beginn verwenden wir PHP 7.1, Composer und Laravel zum schnellen Erstellen unserer PHP-App. Wenn Sie diese Tools noch nicht installiert haben, installieren Sie sie, bevor Sie fortfahren.

Öffnen Sie auf Ihrem Entwicklungscomputer die Eingabeaufforderung oder Shell für ein Verzeichnis, in dem Sie das neue Projekt erstellen möchten. Geben Sie zum Erstellen des Projekts den folgenden Befehl ein.

laravel new php-tutorial

Dadurch werden das Verzeichnis php-tutorial sowie ein Gerüst für das Projekt erstellt und Abhängigkeiten heruntergeladen. Sobald der Befehl abgeschlossen ist, sollten wir sicherstellen, dass alles funktioniert. Ändern Sie das aktuelle Verzeichnis in das Verzeichnis php-tutorial, und führen Sie den folgenden Befehl aus, um den Laravel-Entwicklungsserver zu starten.

php artisan serve

Öffnen Sie Ihren Browser, und navigieren Sie zu http://localhost:8000. Daraufhin sollte die standardmäßige Laravel-Startseite angezeigt werden.

Entwerfen der App

Unsere App ist sehr einfach. Wenn ein Benutzer die Website besucht, sieht er einen Link zum Anmelden und Anzeigen seiner E-Mails. Beim Klicken auf den Link gelangt er zur Azure-Anmeldeseite, wo er sich mit seinem Office 365- oder Outlook.com-Konto anmelden und unserer App Zugriff gewähren kann. Schließlich wird er zurück zu unserer App geleitet, die eine Liste der neuesten E-Mails im Posteingang des Benutzers anzeigt.

Beginnen wir mit dem Erstellen eines Layouts. Erstellen Sie eine neue Datei mit dem Namen layout.blade.php im Verzeichnis ./php-tutorial/resources/views, und fügen Sie den folgenden Code hinzu.

Inhalt der Datei ./php-tutorial/resources/views/layout.blade.php

<!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>

So erhalten wir eine grundlegende HTML-Struktur, und es wird Bootstrap für die Formatierung hinzugefügt.

Nun entfernen wir den Standard-CSS-Code aus ./php-tutorial/public/css/app.css und fügen eine einfache Formatierung hinzu, deshalb verwenden wir nur die Standardwerte von Bootstrap. Öffnen Sie die Datei, und ersetzen Sie den gesamten Inhalt durch Folgendes.

Inhalt der Datei ./php-tutorial/public/css/app.css

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

Nun ändern wir die vorhandene Startseite so, dass dieses Layout verwendet wird. Öffnen Sie die Datei ./php-tutorial/resources/views/welcome.blade.php, und ersetzen Sie den Inhalt durch den folgenden Code.

Inhalt der Datei ./php-tutorial/resources/views/welcome.blade.php

@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/de-de/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://developer.microsoft.com/de-de/graph/" 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

Die Schaltfläche Verbindung mit Outlook herstellen führt noch keine Aktion aus, aber das werden wir bald ändern.

Registrieren der App

Wichtig

Neue App-Registrierungen sollten im Anwendungsregistrierungsportal erstellt und verwaltet werden, damit sie mit Outlook.com kompatibel sind. Erstellen Sie neue App-Registrierungen nur dann im Azure-Verwaltungsportal Folgendes auf Ihre App zutrifft:

  • Sie verwendet den OAuth2 Client Credentials Grant-Fluss oder
  • Sie muss neben Outlook auf andere Office 365-Arbeitslasten zugreifen (z. B. OneDrive for Business oder SharePoint)

Beachten Sie, dass mithilfe des Azure-Verwaltungsportals registrierte Apps nicht mit Outlook.com kompatibel sind und dass Berechtigungsbereiche nicht dynamisch angefordert werden können. Vorhandene App-Registrierungen, die im Azure-Verwaltungsportal erstellt wurden, funktionieren weiterhin nur für Office 365. Diese Registrierungen werden nicht im Anwendungsregistrierungsportal angezeigt und müssen im Azure-Verwaltungsportal verwaltet werden.

Kontoanforderungen

Um das Anwendungsregistrierungsportal zu verwenden, benötigen Sie entweder ein Office 365-Geschäfts- oder Schulkonto oder ein Microsoft-Konto. Wenn Sie nicht über eines dieser Konten verfügen, haben Sie verschiedene Möglichkeiten:

  • Registrieren Sie sich hier für ein neues Microsoft-Konto.
  • Sie haben mehrere Möglichkeiten, ein Office 365-Abonnement zu erhalten:

REST-API-Verfügbarkeit

Die REST-API ist derzeit auf allen Office 365-Konten, die über Exchange Online verfügen, sowie auf allen Outlook.com-Konten aktiviert.

Wechseln Sie zum App-Registrierungsportal, um schnell eine App-ID und einen geheimen Schlüssel abzurufen.

  1. Melden Sie sich über den Link Anmelden mit Ihrem Microsoft-Konto (Outlook.com) oder Ihrem Geschäfts-, Schul- oder Unikonto (Office 365) an.
  2. Klicken Sie auf die Schaltfläche App hinzufügen. Geben Sie php-tutorial für den Namen ein, und klicken Sie auf Anwendung erstellen.
  3. Suchen Sie den Abschnitt Anwendungsgeheimnisse, und klicken Sie auf die Schaltfläche Neues Kennwort generieren. Kopieren Sie nun das Kennwort, und speichern Sie es an einem sicheren Ort. Nachdem Sie das Kennwort kopiert haben, klicken Sie auf Ok.
  4. Suchen Sie den Abschnitt Plattformen, und klicken Sie auf Plattform hinzufügen. Wählen Sie Web aus, und geben Sie dann http://localhost:8000/authorize unter Umleitungs-URIs ein.
  5. Klicken Sie auf Speichern, um die Registrierung abzuschließen. Kopieren Sie die App-ID, und speichern Sie sie zusammen mit dem Kennwort, das Sie zuvor kopiert haben. Wir benötigen diese Werte bald.

So sollten die Details Ihrer App-Registrierung aussehen, wenn Sie fertig sind.

Screenshot der abgeschlossenen App-Registrierung im App-Registrierungsportal

Implementieren von OAuth2

Unser Ziel in diesem Abschnitt ist es, die Schaltfläche auf unserer Homepage so einzurichten, dass der OAuth2-Autorisierungscodegenehmigungs-Fluss mit Azure AD initiiert wird.

Wir verwenden den League OAuth2-Client, um die Autorisierung und die Tokenanforderungen zu verarbeiten. Lassen Sie uns diesen installieren, bevor wir fortfahren.

Öffnen Sie die Datei ./php-tutorial/composer.json, und suchen Sie den require-Eintrag. Aktualisieren Sie diesen Eintrag so, dass "league/oauth2-client": "^2.0" hinzugefügt wird, und speichern Sie die Datei.

Aktualisierter require-Eintrag in ./php-tutorial/composer.json

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

Führen den folgenden Befehl an der Eingabeaufforderung/Shell im Verzeichnis php-tutorial aus.

composer update

Hierdurch werden der OAuth2-Client sowie Abhängigkeiten in Ihrem ./php-tutorial/vendors-Ordner installiert.

Nachdem das Herunterladen der erforderlichen Bibliotheken in Composer abgeschlossen ist, erstellen wir einen neuen Controller, der alle OAuth-Funktionen enthalten soll. Erstellen Sie eine neue Datei im ./php-tutorial/app/Http/Controllers-Verzeichnis mit dem Namen AuthController.php. Fügen Sie den folgenden Code hinzu.

Inhalt der Datei ./php-tutorial/app/Http/Controllers/AuthController.php

<?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();
  }
}

Jetzt müssen wir eine Route zu unserer neuen Controllermethode konfigurieren. Öffnen Sie die Datei ./php-tutorial/routes/web.php, und fügen Sie die folgende Zeile am Ende der Datei hinzu.

Neuer Eintrag in ./php-tutorial/routes/web.php

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

Lassen Sie uns nun die App konfigurieren. Wir werden die Umgebungsvariablen zum Speichern aller OAuth-Details verwenden. Öffnen Sie die Datei ./php-tutorial/.env, und fügen Sie die folgenden Zeilen am Ende der Datei hinzu.

Neue Einträge 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

Ersetzen Sie YOUR_APP_ID_HERE und YOUR_APP_PASSWORD_HERE durch die App-ID und das Kennwort, die bzw. das Sie vom App-Registrierungstool abgerufen haben, und speichern Sie die Datei.

Speichern Sie alle Änderungen, und starten Sie den Laravel-Entwicklungsserver (php artisan serve) neu. Navigieren Sie zu http://localhost:8000, und klicken Sie auf die Schaltfläche Verbindung mit Outlook herstellen. Auf der Seite sollte die Autorisierungs-URL angezeigt werden. Dies sollte folgendermaßen aussehen:

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>

Aktualisieren wir jetzt die App so, dass sie automatisch zu dieser URL navigiert, damit der Benutzer sich anmelden und Zugriff auf die App erteilen kann. Wir fügen auch eine weitere Controllermethode hinzu, um die Umleitung zurück vom Autorisierungsendpunkt aufzunehmen. Ersetzen Sie zunächst die vorhandene signin-Methode in ./php-tutorial/app/Http/Controllers/AuthController.php durch die folgende.

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();
}

Anstatt die URL in die Antwort zu drucken, wird der Clientstatus in der Sitzung gespeichert und der Location-Header zum Umleiten des Browsers verwendet.

Jetzt fügen wir eine authorize-Methode hinzu, um die Umleitung zurück von Azure aufzunehmen und den Autorisierungscode aus der Antwort zu extrahieren. Fügen Sie die folgende Methode zu ./php-tutorial/app/Http/Controllers/AuthController.php hinzu:

Neue gettoken-Methode 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']);
  }
}

Hinzufügen einer neuen Route zu ./php-tutorial/routes/web.php

Neuer Eintrag in ./php-tutorial/routes/web.php

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

Speichern Sie Ihre Änderungen, und navigieren Sie zu http://localhost:8000. Wenn Sie dieses Mal auf die Seite Verbindung mit Outlook herstellen klicken, werden Sie zur Anmeldeseite umgeleitet. Wenn Sie Ihre Office 365- oder Outlook.com-Kontoinformationen eingeben und die angeforderten Berechtigungen akzeptieren, wird der Autorisierungscode in der App angezeigt. Führen wir nun eine Aktion damit aus.

Austauschen des Codes durch ein Token

Lassen Sie uns die gettoken-Methode so aktualisieren, dass der OAuth2-Client verwendet wird, um eine Tokenanforderung vorzunehmen. Zunächst überprüfen wir, ob der in der Umleitung enthaltene Zustand dem Zustand entspricht, den wir nach dem Generieren der Autorisierung in der Sitzung gespeichert haben. Anschließend erstellen wir die eigentliche Tokenanforderung mithilfe des Autorisierungscodes. Ersetzen Sie die vorhandene gettoken durch diese neue.

Aktualisierte gettoken-Funktion 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']);
  }
}

Speichern Sie Ihre Änderungen, und durchlaufen Sie den Anmeldevorgang bei der App erneut. Dieses Mal sollte das Zugriffstoken angezeigt werden.

Aktualisieren des Zugriffstokens

Von Azure zurückgegebene Zugriffstoken sind eine Stunde lang gültig. Wenn Sie das Token verwenden, nachdem es abgelaufen ist, geben die API-Aufrufe 401-Fehler zurück. Sie könnten den Benutzer bitten, sich erneut anzumelden, aber die bessere Option besteht darin, das Token automatisch zu aktualisieren. Lassen Sie uns nun also am Speichern und Aktualisieren der Token (bei Bedarf) arbeiten, bevor wir fortfahren.

Zunächst erstellen wir eine neue Klasse zum Verwalten des Speicherns und Abrufens des Tokens. Da es sich hierbei um ein Beispiel handelt, werden wir der Einfachheit halber alles in der Sitzung speichern. Für Produktionsszenarien wäre es besser, das Aktualisierungstoken in einer sicheren Datenbank zu speichern.

Erstellen Sie einen neuen Ordner im ./php-tutorial/app-Verzeichnis mit dem Namen TokenStore. Erstellen Sie in diesem neuen Ordner eine Datei mit dem Namen TokenCache.php, und fügen Sie den folgenden Code hinzu.

Inhalt der Datei ./php-tutorial/app/TokenStore/TokenCache.php

<?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'];
    }
  }
}

Dadurch werden die drei neue Funktionen hinzugefügt. Die storeTokens-Funktion speichert die Werte in der Sitzung. Die clearTokens-Funktion entfernt diese. Die getAccessToken-Funktion überprüft den Ablauf des aktuellen Zugriffstokens und aktualisiert es, falls erforderlich.

Nun aktualisieren wir die gettoken-Methode so, dass diese neue Klasse zum Speichern der Werte und zum Umleiten von /mail in unserer App verwendet wird. Ersetzen Sie die vorhandene gettoken durch diese neue. Version.

Aktualisierte gettoken-Funktion 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']);
  }
}

Bevor wir fortfahren, implementieren wir eine sehr einfache /mail-Route zum Testen unserer TokenCache-Klasse. Erstellen Sie zuerst einen neuen Controller für unsere E-Mail-Ansicht. Erstellen Sie eine neue Datei im ./php-tutorial/app/Http/Controllers-Verzeichnis mit dem Namen OutlookController.php. Fügen Sie den folgenden Code hinzu.

Inhalt der Datei ./php-tutorial/app/Http/Controllers/OutlookController.php

<?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();
  }
}

Öffnen Sie die Datei ./php-tutorial/routes/web.php, und fügen Sie die folgende Zeile am Ende der Datei hinzu.

Neuer Eintrag in ./php-tutorial/routes/web.php

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

Speichern Sie Ihre Änderungen, wechseln Sie zu http://localhost:8000, und melden Sie sich erneut an. Dieses Mal sollte die App Sie zu https://localhost:8000/mail weiterleiten und das Token anzeigen. Auf diese Weise wird sichergestellt, dass unser Tokencache auf allen Controllern funktioniert. Da wir nun ein Zugriffstoken haben und es bei Bedarf aktualisieren können, sind wir bereit zur Verwendung der Mail-API.

Verwenden der Mail-API

Wir verwenden das Microsoft Graph-SDK für PHP für alle Outlook-API-Aufrufe. Beginnen wir also mit der Installation.

Öffnen Sie die Datei ./php-tutorial/composer.json, und suchen Sie den require-Eintrag. Aktualisieren Sie diesen Eintrag so, dass "microsoft/microsoft-graph": "1.0.*" hinzugefügt wird, und speichern Sie die Datei.

Aktualisierter require-Eintrag 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.*"
},

Führen den folgenden Befehl an der Eingabeaufforderung/Shell im Verzeichnis php-tutorial aus.

composer update

Jetzt werden wir die Funktion mail ändern. Unsere erste Verwendung des Graph-SDKs besteht darin, den Benutzernamen und die E-Mail-Adresse des Benutzers abzurufen. Sie werden bald sehen, warum wir das tun.

Fügen Sie die folgenden Zeilen nach der use App\Http\Controllers\Controller;-Zeile in OutlookController.php hinzu.

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

Ersetzen Sie die vorhandene mail-Funktion durch den folgenden Code.

Aktualisierte mail-Funktion 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().' - '.$user->getMail();
}

Speichern Sie die Änderungen, und aktualisieren Sie die E-Mail-Ansicht. Sie sollten den Namen und die E-Mail-Adresse des authentifizierten Benutzers sehen. (Wenn nicht, fangen Sie von vorne bei http://localhost:8000 an, und melden Sie sich erneut an.)

Fügen wir nun Code zum Abrufen der Nachrichten des Benutzers hinzu. Ersetzen Sie die vorhandene mail-Funktion durch Folgendes.

Aktualisierte mail-Funktion 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().' - '.$user->getMail().'<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)
                    ->addHeaders(array ('X-AnchorMailbox' => $user->getMail()))
                    ->setReturnType(Model\Message::class)
                    ->execute();

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

Zusammenfassung des neuen Codes in der mail-Funktion:

  • Es wird ein Graph-Clientobjekt erstellt und initialisiert, um das an die Funktion übergebene Zugriffstoken zu verwenden.
  • Der X-AnchorMailbox-Header wird auf die Anforderung festgelegt, sodass der API-Endpunkt API-Aufrufe effizienter an den entsprechenden Back-End-Postfachserver weiterleiten kann. Aus diesem Grund haben wir uns die Mühe gemacht und zuvor die E-Mails des Benutzers abgerufen.
  • Die /me/mailfolders/inbox/messages-API wird aufgerufen, um die Posteingangsnachrichten abzurufen, und es werden weitere Methoden zum Steuern der Anforderung verwendet:
    • Die top-Methode wird mit dem Wert 10 verwendet, um die Ergebnisse auf die ersten 10 einzuschränken.
    • Die select-Methode wird verwendet, um nur die Eigenschaften subject, from, receivedDateTime und isRead anzufordern.
    • Die orderby-Methode wird mit dem Wert receivedDateTime desc verwendet, um die neuesten Nachrichten zuerst abzurufen.
  • Die Ergebnisse werden durchlaufen, und der Betreff wird ausgedruckt.

Wenn Sie die App jetzt neu starten, sollten Sie eine sehr einfache Auflistung des Ergebnis-Arrays erhalten. Wir fügen nun etwas HTML und PHP hinzu, um die Ergebnisse etwas ansprechender anzuzeigen.

Anzeigen der Ergebnisse

Wir fügen unserer App eine einfache Anzeige zum Wiedergeben der Nachrichten hinzu. Erstellen Sie eine neue Datei im Ordner ./php-tutorial/resources/views mit dem Namen mail.blade.php, und fügen Sie den folgenden Code hinzu.

Inhalt der Datei ./php-tutorial/resources/views/mail.blade.php

@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

Aktualisieren Sie jetzt die mail -Funktion so, dass diese Ansicht zurückgegeben wird.

Aktualisierte mail-Funktion 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)
                    ->addHeaders(array ('X-AnchorMailbox' => $user->getMail()))
                    ->setReturnType(Model\Message::class)
                    ->execute();

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

Speichern Sie die Änderungen, und aktualisieren Sie die E-Mail-Seite. Jetzt sollte eine ansprechende Liste von Nachrichten wiedergegeben werden.

Die fertige App, in der der Posteingang des Benutzers angezeigt wird.

Hinzufügen von Kalender- und Kontakte-APIs

Da Sie nun das Aufrufen der Outlook-Mail-API gemeistert haben, dürfte es kein Problem mehr sein, das gleiche für Kalender- und Kontakte-APIs zu tun.

Tipp

Wenn Sie die Schritte in diesem Lernprogramm befolgt haben, haben Sie wahrscheinlich ein Zugriffstoken in Ihrem Sitzungscookie gespeichert. Dieses Token ist nur für den Mail.Read-Bereich gültig. Um die Kalender- oder Kontakte-API aufzurufen, müssen wir neue Bereiche hinzufügen. Starten Sie den Browser unbedingt neu, um das Sitzungscookie zu entfernen, damit Sie den Anmeldevorgang von vorne beginnen können, um ein neues Zugriffstoken zu erhalten.

Für die Kalender-API:

  1. Aktualisieren Sie den Wert OAUTH_SCOPES in .\php-tutorial\.env, um den Calendars.Read-Bereich einzuschließen.

    Hinweis: Starten Sie den Entwicklungsserver unbedingt neu, nachdem Sie diese Datei geändert haben.

    OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read Calendars.Read'
    
  2. Fügen Sie eine neue calendar-Funktion zur OutlookController-Klasse in ./php-tutorial/app/Http/Controllers/OutlookController.php hinzu.

    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)
                      ->addHeaders(array ('X-AnchorMailbox' => $user->getMail()))
                      ->setReturnType(Model\Event::class)
                      ->execute();
    
      return view('calendar', array(
        'username' => $user->getDisplayName(),
        'usermail' => $user->getMail(),
        'events' => $events
      ));
    }
    
  3. Fügen Sie eine Route für /calendar zu ./php-tutorial/routes/web.php hinzu.

    Route::get('/calendar', 'OutlookController@calendar')->name('calendar');
    
  4. Fügen Sie eine neue Datei mit dem Namen calendar.blade.php zu ./php-tutorial/resources/views hinzu. Fügen Sie den folgenden Code hinzu.

    @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. Fügen Sie ein Navigationsleistenelement zu ./php-tutorial/resources/views/layout.blade.php direkt nach dem Eintrag für Posteingang hinzu.

    <li class="<?php echo ($_SERVER['REQUEST_URI'] == '/calendar' ? 'active' : '');?>"><a href="/calendar">Calendar</a></li>
    
  6. Starten Sie die App neu. Nach der Anmeldung klicken Sie auf das Element Kalender in der Navigationsleiste.

Für die Kontakte-API:

  1. Aktualisieren Sie den Wert OAUTH_SCOPES in .\php-tutorial\.env, um den Contacts.Read-Bereich einzuschließen.

    Hinweis: Starten Sie den Entwicklungsserver unbedingt neu, nachdem Sie diese Datei geändert haben.

    OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read Contacts.Read'
    
  2. Fügen Sie eine neue contacts-Funktion zur OutlookController-Klasse in ./php-tutorial/app/Http/Controllers/OutlookController.php hinzu.

    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)
                        ->addHeaders(array ('X-AnchorMailbox' => $user->getMail()))
                        ->setReturnType(Model\Contact::class)
                        ->execute();
    
      return view('contacts', array(
        'username' => $user->getDisplayName(),
        'usermail' => $user->getMail(),
        'contacts' => $contacts
      ));
    }
    
  3. Fügen Sie eine Route für /contacts zu ./php-tutorial/routes/web.php hinzu.

    Route::get('/contacts', 'OutlookController@contacts')->name('contacts');
    
  4. Fügen Sie eine neue Datei mit dem Namen contacts.blade.php zu ./php-tutorial/resources/views hinzu. Fügen Sie den folgenden Code hinzu.

    @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. Fügen Sie ein Navigationsleistenelement zu ./php-tutorial/resources/views/layout.blade.php direkt nach dem Eintrag für Posteingang hinzu.

    <li class="<?php echo ($_SERVER['REQUEST_URI'] == '/contacts' ? 'active' : '');?>"><a href="/contacts">Contacts</a></li>
    
  6. Starten Sie die App neu. Nach der Anmeldung klicken Sie auf das Element Kontakte in der Navigationsleiste.