次の方法で共有


Microsoft Graph 用の PHP アプリにユーザー認証を追加する

この記事では、「Microsoft Graph を使用して PHP アプリをビルドする」で作成したアプリケーションにユーザー認証を追加します。 次に、Microsoft Graph ユーザー API を使用して、認証されたユーザーを取得します。

ユーザー認証を追加する

Microsoft Graph SDK には、 PHP League OAuth2 クライアントに基づく認証プロバイダーが含まれています。 ただし、このチュートリアルでは、 デバイス コード フロー を使用してアクセス トークンを取得します。 含まれている認証プロバイダーはこのフローを実装しないため、カスタム アクセス トークン プロバイダーを実装します。

アクセス トークン プロバイダーを作成する

プロジェクトのルート ディレクトリに DeviceCodeTokenProvider.php という名前の新しいファイルを作成します。 次のコードを追加します。

<?php
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

use GuzzleHttp\Client;
use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use Http\Promise\RejectedPromise;
use Microsoft\Kiota\Abstractions\Authentication\AccessTokenProvider;
use Microsoft\Kiota\Abstractions\Authentication\AllowedHostsValidator;

class DeviceCodeTokenProvider implements AccessTokenProvider {

    private string $clientId;
    private string $tenantId;
    private string $scopes;
    private AllowedHostsValidator $allowedHostsValidator;
    private string $accessToken;
    private Client $tokenClient;

    public function __construct(string $clientId, string $tenantId, string $scopes) {
        $this->clientId = $clientId;
        $this->tenantId = $tenantId;
        $this->scopes = $scopes;
        $this->allowedHostsValidator = new AllowedHostsValidator();
        $this->allowedHostsValidator->setAllowedHosts([
            "graph.microsoft.com",
            "graph.microsoft.us",
            "dod-graph.microsoft.us",
            "graph.microsoft.de",
            "microsoftgraph.chinacloudapi.cn"
        ]);
        $this->tokenClient = new Client();
    }

    public function getAuthorizationTokenAsync(string $url, array $additionalAuthenticationContext = []): Promise {
        $parsedUrl = parse_url($url);
        $scheme = $parsedUrl["scheme"] ?? null;

        if ($scheme !== 'https' || !$this->getAllowedHostsValidator()->isUrlHostValid($url)) {
            return new FulfilledPromise(null);
        }

        // If we already have a user token, just return it
        // Tokens are valid for one hour, after that it needs to be refreshed
        if (isset($this->accessToken)) {
            return new FulfilledPromise($this->accessToken);
        }

        // https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-device-code
        $deviceCodeRequestUrl = 'https://login.microsoftonline.com/'.$this->tenantId.'/oauth2/v2.0/devicecode';
        $tokenRequestUrl = 'https://login.microsoftonline.com/'.$this->tenantId.'/oauth2/v2.0/token';

        // First POST to /devicecode
        $deviceCodeResponse = json_decode($this->tokenClient->post($deviceCodeRequestUrl, [
            'form_params' => [
                'client_id' => $this->clientId,
                'scope' => $this->scopes
            ]
        ])->getBody()->getContents());

        // Display the user prompt
        print($deviceCodeResponse->message.PHP_EOL);

        // Response also indicates how often to poll for completion
        // And gives a device code to send in the polling requests
        $interval = (int)$deviceCodeResponse->interval;
        $device_code = $deviceCodeResponse->device_code;

        // Do polling - if attempt times out the token endpoint
        // returns an error
        while (true) {
            sleep($interval);

            // POST to the /token endpoint
            $tokenResponse = $this->tokenClient->post($tokenRequestUrl, [
                'form_params' => [
                    'client_id' => $this->clientId,
                    'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code',
                    'device_code' => $device_code
                ],
                // These options are needed to enable getting
                // the response body from a 4xx response
                'http_errors' => false,
                'curl' => [
                    CURLOPT_FAILONERROR => false
                ]
            ]);

            if ($tokenResponse->getStatusCode() == 200) {
                // Return the access_token
                $responseBody = json_decode($tokenResponse->getBody()->getContents());
                $this->accessToken = $responseBody->access_token;
                return new FulfilledPromise($responseBody->access_token);
            } else if ($tokenResponse->getStatusCode() == 400) {
                // Check the error in the response body
                $responseBody = json_decode($tokenResponse->getBody()->getContents());
                if (isset($responseBody->error)) {
                    $error = $responseBody->error;
                    // authorization_pending means we should keep polling
                    if (strcmp($error, 'authorization_pending') != 0) {
                        return new RejectedPromise(
                            new Exception('Token endpoint returned '.$error, 100));
                    }
                }
            }
        }
    }

    public function getAllowedHostsValidator(): AllowedHostsValidator {
        return $this->allowedHostsValidator;
    }
}
?>

ユーザー認証用に Graph クライアントを構成する

次に、 DeviceCodeTokenProvider クラスを使用して、 デバイス コード フローを使用してアクセス トークンを要求します。

  1. プロジェクトのルート ディレクトリに GraphHelper.php という名前の新しいファイルを作成します。 次のコードを追加します。

    <?php
    class GraphHelper {
    }
    ?>
    
  2. PHP タグ内に次の using ステートメントを追加します。

    use Microsoft\Graph\Generated\Models;
    use Microsoft\Graph\Generated\Users\Item\MailFolders\Item\Messages\MessagesRequestBuilderGetQueryParameters;
    use Microsoft\Graph\Generated\Users\Item\MailFolders\Item\Messages\MessagesRequestBuilderGetRequestConfiguration;
    use Microsoft\Graph\Generated\Users\Item\SendMail\SendMailPostRequestBody;
    use Microsoft\Graph\Generated\Users\Item\UserItemRequestBuilderGetQueryParameters;
    use Microsoft\Graph\Generated\Users\Item\UserItemRequestBuilderGetRequestConfiguration;
    use Microsoft\Graph\GraphRequestAdapter;
    use Microsoft\Graph\GraphServiceClient;
    use Microsoft\Kiota\Abstractions\Authentication\BaseBearerTokenAuthenticationProvider;
    
    require_once 'DeviceCodeTokenProvider.php';
    
  3. GraphHelper クラスに以下のコードを追加します。

    private static string $clientId = '';
    private static string $tenantId = '';
    private static string $graphUserScopes = '';
    private static DeviceCodeTokenProvider $tokenProvider;
    private static GraphServiceClient $userClient;
    
    public static function initializeGraphForUserAuth(): void {
        GraphHelper::$clientId = $_ENV['CLIENT_ID'];
        GraphHelper::$tenantId = $_ENV['TENANT_ID'];
        GraphHelper::$graphUserScopes = $_ENV['GRAPH_USER_SCOPES'];
    
        GraphHelper::$tokenProvider = new DeviceCodeTokenProvider(
            GraphHelper::$clientId,
            GraphHelper::$tenantId,
            GraphHelper::$graphUserScopes);
        $authProvider = new BaseBearerTokenAuthenticationProvider(GraphHelper::$tokenProvider);
        $adapter = new GraphRequestAdapter($authProvider);
        GraphHelper::$userClient = GraphServiceClient::createWithRequestAdapter($adapter);
    }
    
  4. メイン.phpの空のinitializeGraph関数次のように置き換えます。

    function initializeGraph(): void {
        GraphHelper::initializeGraphForUserAuth();
    }
    

このコードは、.env ファイルから情報を読み込み、 DeviceCodeTokenProvider オブジェクトと GraphServiceClient オブジェクトの 2 つのプロパティを初期化します。 DeviceCodeTokenProvider オブジェクトはアクセス トークンを要求するために使用され、GraphServiceClient オブジェクトを使用して Microsoft Graph を呼び出します。

デバイス コード フローをテストする

次に、 GraphHelperからアクセス トークンを取得するコードを追加します。

  1. 次の関数を GraphHelper クラスに追加します。

    public static function getUserToken(): string {
        return GraphHelper::$tokenProvider
            ->getAuthorizationTokenAsync('https://graph.microsoft.com')->wait();
    }
    
  2. メイン.phpの空のdisplayAccessToken関数次のように置き換えます。

    function displayAccessToken(): void {
        try {
            $token = GraphHelper::getUserToken();
            print('User token: '.$token.PHP_EOL.PHP_EOL);
        } catch (Exception $e) {
            print('Error getting access token: '.$e->getMessage().PHP_EOL.PHP_EOL);
        }
    }
    
  3. アプリをビルドして実行します。 オプションの入力を求められたら、「 1 」と入力します。 アプリケーションに URL とデバイス コードが表示されます。

    $ php main.php
    
    PHP Graph Tutorial
    
    Please choose one of the following options:
    0. Exit
    1. Display access token
    2. List my inbox
    3. Send mail
    4. Make a Graph call
    1
    To sign in, use a web browser to open the page https://microsoft.com/devicelogin and
    enter the code RB2RUD56D to authenticate.
    
  4. ブラウザーを開き、表示された URL を参照します。 指定したコードを入力し、サインインします。

    重要

    https://microsoft.com/deviceloginを参照するときにブラウザーにログインしている既存の Microsoft 365 アカウントに注意してください。 プロファイル、ゲスト モード、プライベート モードなどのブラウザー機能を使用して、テストに使用するアカウントとして認証することを確認します。

  5. 完了したら、アプリケーションに戻り、アクセス トークンを表示します。

    ヒント

    検証とデバッグ のみを目的として、 https://jwt.msで Microsoft のオンライン トークン パーサーを使用してユーザー アクセス トークンをデコードできます (職場または学校アカウントの場合のみ)。 トークンの解析は、Microsoft Graph を呼び出すときにトークン エラーが発生した場合に役立ちます。 たとえば、トークン内の scp 要求に、想定される Microsoft Graph アクセス許可スコープが含まれていることを確認します。

ユーザーを取得する

認証が構成されたので、最初の Microsoft Graph API呼び出しを行うことができます。 認証されたユーザーの名前と電子メール アドレスを取得するコードを追加します。

  1. GraphHelper クラスに以下のコードを追加します。

    public static function getUser(): Models\User {
        $configuration = new UserItemRequestBuilderGetRequestConfiguration();
        $configuration->queryParameters = new UserItemRequestBuilderGetQueryParameters();
        $configuration->queryParameters->select = ['displayName','mail','userPrincipalName'];
        return GraphHelper::$userClient->me()->get($configuration)->wait();
    }
    
  2. メイン.phpの空のgreetUser関数次のように置き換えます。

    function greetUser(): void {
        try {
            $user = GraphHelper::getUser();
            print('Hello, '.$user->getDisplayName().'!'.PHP_EOL);
    
            // For Work/school accounts, email is in Mail property
            // Personal accounts, email is in UserPrincipalName
            $email = $user->getMail();
            if (empty($email)) {
                $email = $user->getUserPrincipalName();
            }
            print('Email: '.$email.PHP_EOL.PHP_EOL);
        } catch (Exception $e) {
            print('Error getting user: '.$e->getMessage().PHP_EOL.PHP_EOL);
        }
    }
    

アプリを今すぐ実行すると、サインインした後に名前で歓迎されます。

Hello, Megan Bowen!
Email: MeganB@contoso.com

コードの説明

getUser関数のコードについて考えてみましょう。 ほんの数行ですが、注意する必要がある重要な詳細がいくつかあります。

'me' へのアクセス

関数は 、Get ユーザー API に対する要求を作成します。 この API には、次の 2 つの方法でアクセスできます。

GET /me
GET /users/{user-id}

この場合、コードは GET /me API エンドポイントを呼び出します。 このエンドポイントは、ユーザー ID を知らずに認証されたユーザーを取得するためのショートカット メソッドです。

注:

GET /me API エンドポイントは認証されたユーザーを取得するため、ユーザー認証を使用するアプリでのみ使用できます。 アプリ専用認証アプリは、このエンドポイントにアクセスできません。

特定のプロパティの要求

関数は 、$select クエリ パラメーター を使用して、必要なプロパティのセットを指定します。

厳密に型指定された戻り値の型

関数は、API からの JSON 応答から逆シリアル化された User オブジェクトを返します。 コードでは $selectが使用されるため、返される User オブジェクトには、要求されたプロパティのみが値を持ちます。 その他のすべてのプロパティには既定値があります。

次の手順