Compartir a través de


Agregar autenticación de usuario a aplicaciones PHP para Microsoft Graph

En este artículo, agregará autenticación de usuario a la aplicación que creó en Compilación de aplicaciones PHP con Microsoft Graph. A continuación, use la API de usuario de Microsoft Graph para obtener el usuario autenticado.

Agregar autenticación de usuario

El SDK de Microsoft Graph incluye proveedores de autenticación basados en el cliente OAuth2 de php League. Sin embargo, en este tutorial, usará el flujo de código del dispositivo para obtener tokens de acceso. Los proveedores de autenticación incluidos no implementan este flujo, por lo que se implementa un proveedor de tokens de acceso personalizado.

Creación de un proveedor de tokens de acceso

Cree un nuevo archivo en el directorio raíz del proyecto denominado DeviceCodeTokenProvider.php. Agregue el siguiente código.

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

Configuración del cliente de Graph para la autenticación de usuario

Ahora, use la DeviceCodeTokenProvider clase para solicitar un token de acceso mediante el flujo de código del dispositivo.

  1. Cree un nuevo archivo en el directorio raíz del proyecto denominado GraphHelper.php. Agregue el siguiente código.

    <?php
    class GraphHelper {
    }
    ?>
    
  2. Agregue las siguientes using instrucciones dentro de las etiquetas PHP.

    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. Agregue el código siguiente a la clase 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. Reemplace la función vacía initializeGraph en main.php por lo siguiente.

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

Este código carga información del archivo .env e inicializa dos propiedades, un DeviceCodeTokenProvider objeto y un GraphServiceClient objeto . El DeviceCodeTokenProvider objeto se usa para solicitar un token de acceso y el GraphServiceClient objeto se usa para realizar llamadas a Microsoft Graph.

Prueba del flujo de código del dispositivo

A continuación, agregue código para obtener un token de acceso de GraphHelper.

  1. Agregue la siguiente función a la clase GraphHelper.

    public static function getUserToken(): string {
        return GraphHelper::$tokenProvider
            ->getAuthorizationTokenAsync('https://graph.microsoft.com')->wait();
    }
    
  2. Reemplace la función vacía displayAccessToken en main.php por lo siguiente.

    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. Compile y ejecute la aplicación. Escriba 1 cuando se le solicite una opción. La aplicación muestra una dirección URL y un código de dispositivo.

    $ 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. Abra un explorador y vaya a la dirección URL que se muestra. Escriba el código proporcionado e inicie sesión.

    Importante

    Tenga en cuenta cualquier cuenta de Microsoft 365 existente que haya iniciado sesión en el explorador al navegar a https://microsoft.com/devicelogin. Use características del explorador como perfiles, modo invitado o modo privado para asegurarse de autenticarse como la cuenta que quiere usar para las pruebas.

  5. Una vez completado, vuelva a la aplicación para ver el token de acceso.

    Sugerencia

    Solo con fines de validación y depuración, puede descodificar tokens de acceso de usuario (solo para cuentas profesionales o educativas) mediante el analizador de tokens en línea de Microsoft en https://jwt.ms. El análisis del token puede ser útil si se producen errores de token al llamar a Microsoft Graph. Por ejemplo, comprobar que la scp notificación del token contiene los ámbitos de permiso de Microsoft Graph esperados.

Obtener usuario

Ahora que la autenticación está configurada, puede realizar la primera llamada de Microsoft Graph API. Agregue código para obtener el nombre y la dirección de correo electrónico del usuario autenticado.

  1. Agregue el código siguiente a la clase 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. Reemplace la función vacía greetUser en main.php por lo siguiente.

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

Si ejecuta la aplicación ahora, después de iniciar sesión, le da la bienvenida por su nombre.

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

Código explicado

Tenga en cuenta el código de la getUser función . Son solo unas pocas líneas, pero hay algunos detalles clave que debe tener en cuenta.

Acceso a 'me'

La función compila una solicitud a la API Get user . Esta API es accesible de dos maneras:

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

En este caso, el código llama al punto de conexión de API GET /me . Este punto de conexión es un método abreviado para obtener el usuario autenticado sin conocer su identificador de usuario.

Nota:

Dado que el GET /me punto de conexión de API obtiene el usuario autenticado, solo está disponible para las aplicaciones que usan la autenticación de usuario. Las aplicaciones de autenticación de solo aplicación no pueden acceder a este punto de conexión.

Solicitud de propiedades específicas

La función usa el parámetro de consulta $select para especificar el conjunto de propiedades que necesita.

Tipo de valor devuelto fuertemente tipado

La función devuelve un User objeto deserializado a partir de la respuesta JSON de la API. Dado que el código usa $select, solo las propiedades solicitadas tienen valores en el objeto devuelto User . Todas las demás propiedades tienen valores predeterminados.

Paso siguiente