Outlook のメール、予定表、および連絡先を取得する PHP アプリの記述Write a PHP app to get Outlook mail, calendar, and contacts

このガイドの目的は、Office 365 または Outlook.com でメッセージを取得する単純な PHP アプリを作成するプロセスについて順を追って説明することです。このリポジトリ内のソースコードは、ここで概説する手順に従った場合の最終結果を示しています。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.

このガイドでは、Outlook メールのアクセスに Microsoft Graph を使用します。Microsoft は、Outlook のメール、予定表、連絡先へのアクセスに Microsoft Graph を使用することをお勧めします。Graph エンドポイントで使用できない機能が必要な場合のみ、Outlook API を直接 (https://outlook.office.com/api から) 使用します。このサンプルの Outlook API を使用したバージョンについては、このブランチを参照してください。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.

このガイドでは、PHP がインストール済みで、開発用コンピューターで動作することを前提としています。チュートリアルは、PHP 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.

アプリを作成するCreate the app

では始めましょう。最初に、PHP 7.1ComposerLaravel を使用して PHP アプリを簡単に作成します。これらのツールをまだインストールしていない場合は、続行する前にインストールしてください。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.

開発用マシンでは、新しいプロジェクトを作成するディレクトリでコマンド プロンプトまたはシェルを開きます。次のコマンドを入力してプロジェクトを作成します。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

これによって新しい php-tutorial ディレクトリが作成され、プロジェクトがスキャフォールディングされて依存関係がダウンロードされます。コマンドが完了したら、すべて動作していることを確認します。現在のディレクトリを php-tutorial ディレクトリに変更し、次のコマンドを実行して 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

ブラウザーを開き、http://localhost:8000 を表示します。既定の Laravel 開始ページが表示されます。Open your brower and browse to http://localhost:8000. You should see a default Laravel start page.

アプリを設計するDesigning the app

アプリは非常にシンプルになります。ユーザーがサイトにアクセスすると、ログインして、自分の電子メールを表示するためのリンクが表示されます。そのリンクをクリックすると、ユーザーは Azure のログイン ページに移動します。このページで、ユーザーは自分の Office 365 または Outlook.com のアカウントを使用してログインし、アプリへのアクセス権を付与できます。最後に、ユーザーはアプリにリダイレクトされます。アプリは、ユーザーの受信トレイに最新の電子メールの一覧を表示します。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.

まず、レイアウトの作成から開始しましょう。./php-tutorial/resources/views ディレクトリに layout.blade.php という名前の新しいファイルを作成し、次のコードを追加します。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.

./php-tutorial/resources/views/layout.blade.php ファイルのコンテンツContents 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>

これは、基本的な HTML 構造を示すものであり、スタイル指定には Bootstrap を追加します。This just gives us a basic HTML structure and adds Bootstrap for styling.

ここで、./php-tutorial/public/css/app.css から既定の CSS コードを削除し、基本的なスタイル指定をいくつか追加して、Bootstrap の既定値だけを使用します。ファイルを開きます。そのファイル全体のコンテンツは次のようになっています。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.

./php-tutorial/public/css/app.css ファイルのコンテンツContents of the ./php-tutorial/public/css/app.css file

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

次に、このレイアウトを使用するように既存のホーム ページを変更します。./php-tutorial/resources/views/welcome.blade.php ファイルを開き、そのコンテンツを次のコードに置き換えます。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.

./php-tutorial/resources/views/welcome.blade.php ファイルのコンテンツContents 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

[Outlook に接続] ボタンはまだ何も機能しませんが、すぐに修正します。The Connect to Outlook button doesn't do anything yet, but we'll fix that soon.

アプリを登録するRegister the app

重要

アカウントの要件Account requirements

Azure Active Directory 管理センターを使用するには、Office 365 の職場または学校のアカウントか、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. どれもお持ちでない場合、次の各種オプションがあります。If you don't have either of these, you have a number of options:

  • ここから新しく Microsoft アカウントにサインアップします。Sign up for a new Microsoft account here.
  • Office 365 サブスクリプションは、いくつかの異なる方法で取得できます。You can obtain an Office 365 subscription in a couple of different ways:
  1. ブラウザーを開き、Azure Active Directory 管理センターへ移動します。Open a browser and navigate to the Azure Active Directory admin center. 個人用アカウント (別名: Microsoft アカウント)、または職場/学校アカウントを使用してログインします。Login using a personal account (aka: Microsoft Account) or Work or School Account.

  2. 左側のナビゲーションで [Azure Active Directory] を選択し、それから [管理][アプリの登録 (プレビュー)] を選択します。Select Azure Active Directory in the left-hand navigation, then select App registrations (Preview) under Manage.

  3. [新規登録] を選択します。Select New registration. [アプリケーションを登録] ページで、次のように値を設定します。On the Register an application page, set the values as follows.

    • PHP Outlook Tutorial[名前] を設定します。Set Name to PHP Outlook Tutorial.
    • [サポートされているアカウントの種類][任意の組織のディレクトリ内のアカウントと個人用の Microsoft アカウント] に設定します。Set Supported account types to Accounts in any organizational directory and personal Microsoft accounts.
    • [リダイレクト URI] で、最初のドロップダウン リストを Web に設定し、それから http://localhost:8000/authorize に値を設定します。Under Redirect URI, set the first drop-down to Web and set the value to http://localhost:8000/authorize.
  4. [登録] を選択します。Choose Register. PHP Outlook チュートリアル ページで、アプリケーション (クライアント) の ID の値をコピーして保存し、次の手順に移ります。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. [管理] の下の [認証] を選択します。Select Authentication under Manage. [暗黙的な許可] セクションを検索し、[ID トークン] を有効にします。Locate the Implicit grant section and enable ID tokens. [保存] を選択します。Choose Save.

  6. [管理][証明書とシークレット] を選択します。Select Certificates & secrets under Manage. [新しいクライアント シークレット] ボタンを選択します。Select the New client secret button. [説明] に値を入力して、[有効期限] のオプションのいずれかを選び、[追加] を選択します。Enter a value in Description and select one of the options for Expires and choose Add.

  7. クライアント シークレットの値をコピーしてから、このページから移動します。Copy the client secret value before you leave this page. 次の手順で行います。You will need it in the next step.

    重要

    このクライアント シークレットは今後表示されないため、今必ずコピーするようにしてください。This client secret is never shown again, so make sure you copy it now.

OAuth2 を実装するImplementing OAuth2

このセクションの目標は、ホーム ページ上のボタンから Azure AD による OAuth2 承認コードの付与フローを開始することです。Our goal in this section is to make the button on our home page initiate the OAuth2 Authorization Code Grant flow with Azure AD.

認証とトークンの要求の処理には、League OAuth 2 クライアントを使用します。続行する前にインストールします。We'll use the League OAuth 2 Client to handle our authorization and token requests. Let's install that before proceeding.

./php-tutorial/composer.json ファイルを開き、require エントリを探します。このエントリを更新して "league/oauth2-client": "^2.0" を追加し、ファイルを保存します。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.

./php-tutorial/composer.json で更新済みの require エントリUpdated 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"
},

php-tutorial ディレクトリで、コマンド プロンプト/シェルから次のコマンドを実行します。Run the following command from your command prompt/shell in the php-tutorial directory.

composer update

これによって OAuth 2 クライアントと依存関係が ./php-tutorial/vendors フォルダーにインストールされます。This will install the OAuth 2 client and dependencies into your ./php-tutorial/vendors folder.

Composer による必要なライブラリのダウンロードが完了したら、OAuth の機能をすべて含む新しいコントローラーを作成します。./php-tutorial/app/Http/Controllers ディレクトリに AuthController.php という名前の新しいファイルを作成します。次のコードを追加します。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.

./php-tutorial/app/Http/Controllers/AuthController.php ファイルのコンテンツContents 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();
  }
}

ここで、新しいコントローラー メソッドへのルートを構成する必要があります。./php-tutorial/routes/web.php ファイルを開き、ファイルの下部に次の行を追加します。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.

./php-tutorial/routes/web.php の新しいエントリNew entry in ./php-tutorial/routes/web.php

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

次に、アプリの設定を行います。OAuth の詳細をすべて保持するために、環境変数を使用します。./php-tutorial/.env ファイルを開き、下部に次のコードを追加します。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.

./php-tutorial/.env の新しいエントリNew 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

YOUR_APP_ID_HEREYOUR_APP_PASSWORD_HERE をアプリ登録ツールで取得したアプリケーション ID とパスワードに置き換え、ファイルを保存します。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.

すべての変更を保存し、Laravel 開発サーバー (php artisan serve) を再起動します。http://localhost:8000 を表示し、[Outlook への接続] をクリックします。次に示すように、承認 URL がページに出力されます。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>

次にアプリを更新し、実際にその URL に移動して、ユーザーがサインインでき、そのアプリへのアクセスが許可されるようにします。また、別のコントローラー メソッドを追加して、Azure 承認エンドポイントからのリダイレクトを受信できるようにします。最初に、./php-tutorial/app/Http/Controllers/AuthController.php の既存の signin メソッドをこれと置き換えます。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();
}

応答に URL を出力するのではなく、クライアントの状態をセッションに保存し、Location ヘッダーをブラウザーのリダイレクトに使用します。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.

ここで、authorize メソッドを追加して Azure からリダイレクトを受信し、応答から承認コードを抽出できるようにします。次のメソッドを ./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.

./php-tutorial/app/Http/Controllers/AuthController.php の新しい gettoken メソッドNew 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']);
  }
}

./php-tutorial/routes/web.php への新しいルートの追加Add a new route in ./php-tutorial/routes/web.php

./php-tutorial/routes/web.php の新しいエントリNew entry in ./php-tutorial/routes/web.php

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

変更を保存し、http://localhost:8000 を参照します。ここでは、[Outlook に接続] をクリックするとログイン ページにリダイレクトされます。Office 365 または Outlook.com アカウント情報を入力して、要求したアクセス許可が承認された場合、アプリに認証コードが表示されます。これについて操作してみます。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.

トークン用にコードを交換するExchanging the code for a token

OAuth 2 クライアントを使用してトークンを要求するために、gettoken メソッドを更新します。最初に、リダイレクトに含まれている状態が、承認 URL の生成後にセッションに保存された状態と一致していることを確認し、認証コードを使って実際にトークンを要求します。既存の gettoken を、以下の新しい関数で置き換えます。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.

./php-tutorial/app/Http/Controllers/AuthController.php の更新された gettoken 関数Updated 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']);
  }
}

変更を保存し、もう一度アプリのログイン プロセスを実行します。今回はアクセス トークンが表示されます。Save your changes and go through the login process in your app again. This time you should see the access token displayed.

アクセス トークンを更新するRefreshing the access token

Azure から返されるアクセス トークンの有効期限は 1 時間です。期限切れになった後にトークンを使用すると、API 呼び出しから 401 エラーが返されます。ユーザーにもう一度サインインするよう求めることもできますが、それより良い方法として、トークンを更新することができます。それでは、トークンを保存し、必要であれば続行する前に更新しましょう。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.

最初に、トークンの保存と取得を管理する新しいクラスを作成しましょう。これはサンプルのため、簡単なものにし、すべてセッション内に保存しておきます。運用シナリオでは、セキュリティ保護されたデータベースに更新トークンを保存することをお勧めします。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.

./php-tutorial/app ディレクトリに TokenStore という名前の新しいフォルダーを作成します。この新しいフォルダーに、TokenCache.php というファイルを作成し、次のコードを追加します。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.

./php-tutorial/app/TokenStore/TokenCache.php ファイルのコンテンツContents 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'];
    }
  }
}

これによって 3 つの新しい関数を追加します。storeTokens 関数はセッション内に値を保存します。clearTokens 関数はそれを削除します。getAccessToken 関数は現在のアクセス トークンの有効期限を確認し、必要に応じて更新します。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.

ここでこの新しいクラスを使用するよう gettoken メソッドを更新し、値を保存してアプリ内の /mail にリダイレクトします。既存の gettoken を、以下の新しいバージョンで置き換えます。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.

./php-tutorial/app/Http/Controllers/AuthController.php の更新された gettoken 関数Updated 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']);
  }
}

続行する前に、非常に簡単な /mail ルートを実装して TokenCache クラスをテストします。最初にメール ビューの新しいコントローラーを作成します。./php-tutorial/app/Http/Controllers ディレクトリに OutlookController.php という名前の新しいファイルを作成します。次のコードを追加します。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.

./php-tutorial/app/Http/Controllers/OutlookController.php ファイルのコンテンツContents 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();
  }
}

./php-tutorial/routes/web.php ファイルを開き、ファイルの下部に次の行を追加します。Open the ./php-tutorial/routes/web.php file and add the following line at the bottom of the file.

./php-tutorial/routes/web.php の新しいエントリNew entry in ./php-tutorial/routes/web.php

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

変更を保存し、http://localhost:8000 を参照してもう一度サインインします。この時点では、アプリは https://localhost:8000/mail にリダイレクトされ、トークンが表示されてトークン キャッシュがコントローラー全体で動作していることを確認できます。これで、アクセス トークンが用意され、必要に応じて更新できるようになったため、メール API を使用する準備が整いました。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.

メール API を使用するUsing the Mail API

Microsoft Graph SDK for PHP を使用してすべての Outlook API 呼び出しを実行するため、最初にこれをインストールします。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.

./php-tutorial/composer.json ファイルを開き、require エントリを探します。このエントリを更新して "microsoft/microsoft-graph": "1.0.*" を追加し、ファイルを保存します。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.

./php-tutorial/composer.json で更新済みの require エントリUpdated 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.*"
},

php-tutorial ディレクトリで、コマンド プロンプト/シェルから次のコマンドを実行します。Run the following command from your command prompt/shell in the php-tutorial directory.

composer update

ここで mail 関数を変更します。Now let's modify the mail function. ユーザー名を取得する際に、はじめてここで Graph SDK を使用し、Microsoft Graph API の呼び出しが行えることを確認します。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.

OutlookController.phpuse App\Http\Controllers\Controller; 行のすぐ後に次の行を追加します。Add the following lines just after the use App\Http\Controllers\Controller; line in OutlookController.php.

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

既存の mail 関数を、以下のコードで置換します。Replace the existing mail function with the following code.

./php-tutorial/app/Http/Controllers/OutlookController.php の更新された mail 関数Updated 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();
}

変更を保存し、メール ビューを更新します。Save your changes and refresh the mail view. 認証されたユーザーの名前が表示されます。You should see the authenticated user's name. (表示されない場合は、http://localhost:8000 からやり直して再度ログインします。)(If not, start over at http://localhost:8000 and login again.)

ここで、ユーザーのメッセージを取得するコードを追加します。既存の mail 関数を、以下の関数で置換します。Now let's add code to retrieve the user's messages. Replace the existing mail function with the following.

./php-tutorial/app/Http/Controllers/OutlookController.php の更新された mail 関数Updated 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/>';
  }
}

mail 関数の新しいコードの実行内容をまとめると、次のようになります。To summarize the new code in the mail function:

  • 関数に渡されるアクセス トークンを使用できるよう、Graph のクライアント オブジェクトを作成して初期化します。It creates a Graph client object and initializes it to use the access token passed to the function.
  • API /me/mailfolders/inbox/messages を呼び出して、受信トレイのメッセージを取得し、要求の制御には他のメソッドを使用します。It calls the /me/mailfolders/inbox/messages API to get inbox messages, and uses other methods to control the request:
    • top メソッドを値 10 で使用して、結果を最初の 10 に制限します。It uses the top method with a value of 10 to limit the results to the first 10.
    • select メソッドは、subjectfromreceivedDateTimeisRead のプロパティの要求のみに使用します。It uses the select method to only request the subject, from, receivedDateTime, and isRead properties.
    • 値が receivedDateTime descorderby メソッドを使用して、まず最新のメッセージを取得します。It uses the orderby method with a value of receivedDateTime desc to get the newest messages first.
  • 結果をループ処理し、件名を出力します。It loops over the results and prints out the subject.

ここでアプリを再起動すると、非常に簡単な結果の配列リストが表示されます。結果を見やすく表示するため、一部 HTML と PHP を追加します。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.

結果を表示するDisplaying the results

簡易ビューをアプリに追加して、メッセージを表示します。./php-tutorial/resources/views フォルダーに mail.blade.php という新しいファイルを作成し、次のコードを追加します。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.

./php-tutorial/resources/views/mail.blade.php ファイルのコンテンツContents 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

mail 関数を更新してこのビューを返します。Now update the mail function to return this view.

./php-tutorial/app/Http/Controllers/OutlookController.php の更新された mail 関数Updated 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
  ));
}

変更を保存し、メール ページを更新します。これで、メッセージのリストが見やすく表示されます。Save your changes and refresh the mail page. You should now get a nicely rendered list of messages.

完了したアプリに表示されるユーザーの受信トレイ。

予定表 API と連絡先 API を追加するAdding Calendar and Contacts APIs

ここまでで Outlook メール API の呼び出しを習得しました。予定表 API と連絡先 API の呼び出しも同様に簡単です。Now that you've mastered calling the Outlook Mail API, doing the same for Calendar and Contacts APIs is similar and easy.

ヒント

このチュートリアルに沿って作業を進めたなら、アクセス トークンはセッション Cookie に保存されているはずです。そのトークンは Mail.Read のスコープでのみ有効です。予定表 API や連絡先 API を呼び出すためには、新しいスコープを追加する必要があります。ブラウザーを再起動して、セッション Cookie をすべて削除すると、ログイン処理を最初から行って新しいアクセス トークンを取得できます。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.

予定表 API の場合:For Calendar API:

  1. .\php-tutorial\.envOAUTH_SCOPES の値を更新して Calendars.Read スコープを含めます。Update the OAUTH_SCOPES value in .\php-tutorial\.env to include the Calendars.Read scope.

    注意

    このファイルを変更したら、必ず開発サーバーを再起動してください。Be sure to restart the development server after changing this file!

    OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read Calendars.Read'
    
  2. ./php-tutorial/app/Http/Controllers/OutlookController.phpOutlookController クラスに新しい関数 calendar を追加します。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. /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. ./php-tutorial/resources/viewscalendar.blade.php という新しいファイルを追加します。次のコードを追加します。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. 受信トレイのエントリの直後に ./php-tutorial/resources/views/layout.blade.php にナビゲーション バー アイテムを追加します。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. アプリを再起動します。サインインした後、ナビゲーション バーの [予定表] アイテムをクリックします。Restart the app. After signing in, click the Calendar item on the nav bar.

連絡先 API について:For Contacts API:

  1. .\php-tutorial\.envOAUTH_SCOPES の値を更新して Contacts.Read スコープを含めます。Update the OAUTH_SCOPES value in .\php-tutorial\.env to include the Contacts.Read scope.

    注意

    このファイルを変更したら、必ず開発サーバーを再起動してください。Be sure to restart the development server after changing this file!

    OAUTH_SCOPES='openid profile offline_access User.Read Mail.Read Contacts.Read'
    
  2. ./php-tutorial/app/Http/Controllers/OutlookController.phpOutlookController クラスに新しい関数 contacts を追加します。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. /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. ./php-tutorial/resources/viewscontacts.blade.php という新しいファイルを追加します。次のコードを追加します。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. 受信トレイのエントリの直後に ./php-tutorial/resources/views/layout.blade.php にナビゲーション バー アイテムを追加します。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. アプリを再起動します。サインインした後、ナビゲーション バーの [連絡先] アイテムをクリックします。Restart the app. After signing in, click the Contacts item on the nav bar.