microsoftgraph/msgraph-sdk-php

Web API Token and Refresh Token

Opened this issue · 11 comments

Hi,
I am very new to MS Graph API, but have successfully developed connectivity with other product APIs such as Xero.

I have an open item TrackingID#2410040050002672 and was advised to post a question here.

Using the PHP SDK, I am struggling with the generating a token & refresh token, then storing both in the InMemoryAccessTokenCache for later use. It is my understanding that once this method has been established, I can call the InMemoryAccessTokenCache. If the token is still valid a valid token is returned, or, if the token is expired a refresh token returned.

My setup is complete and I can get a token using postman.

I have been advised to use the following code to establish the token & refresh token, and store them InMemoryAccessTokenCache. However, this fails as it seems Microsoft\Graph\Auth\OAuth2\OAuth2Client does not exist in the SDK !

use Microsoft\Graph\Graph;
use Microsoft\Graph\Http\GraphRequest;
use Microsoft\Graph\Core\GraphConstants;
use Microsoft\Graph\Auth\OAuth2\InMemoryAccessTokenCache;
use Microsoft\Graph\Auth\OAuth2\OAuth2Client;
	
require 'vendor/autoload.php';
$tenantId = 'MY TENANT ID';	
$clientId = 'MY CLIENT ID';
$clientSecret = 'MY SECRET';
$scopes = 'https://graph.microsoft.com/.default';
		
$oauthClient = new OAuth2Client($clientId, $clientSecret, $tenantId, $scopes);
$tokenCache = new InMemoryAccessTokenCache();
		
$accessToken = $oauthClient->getAccessToken($tokenCache);
		
$graph = new Graph();
$graph->setAccessToken($accessToken);
	
// Example request to get users		
$request = $graph->createRequest("GET", "/users")
		->setReturnType(\Microsoft\Graph\Model\User::class);
$users = $request->execute();
		
foreach ($users as $user) {
	echo "User: " . $user->getDisplayName() . "\n";
}

I would appreciate any help in configuring this (it's diving me mad!!).

It is also my understanding that once the token and refresh token has been established, I can use the following to establish/re-extablish the token, prior and API call to my designated endpoint ????

use Microsoft\Kiota\Authentication\Cache\InMemoryAccessTokenCache;
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAccessTokenProvider;
use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAuthenticationProvider;
use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;

require 'vendor/autoload.php';

$tokenRequestContext = new ClientCredentialContext(
	'MY TENANT ID',
	'MY CLIENT ID',
	'MY SECRET'
);
	
$graphServiceClient = new GraphServiceClient($tokenRequestContext);
	
$scopes = ['https://graph.microsoft.com/.default'];

$inMemoryCache = new InMemoryAccessTokenCache();

$graphServiceClient = GraphServiceClient::createWithAuthenticationProvider(
	GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
		GraphPhpLeagueAccessTokenProvider::createWithCache(
			$inMemoryCache,
			$tokenRequestContext,
			$scopes
		)
	)
);

$accessToken = $inMemoryCache->getTokenWithContext($tokenRequestContext);

Honestly, it's day 4 of going in circles, please help?
Thank you

Thanks for raising this @thorby68

To help understand this better, any chance you've been able to come across the docs at this link?
https://github.com/microsoftgraph/msgraph-sdk-php/blob/main/docs/Examples.md#access-token-management

I believe the first example would be incorrect as it uses imports and types that do not exist in the latest version of the php sdk(v2.x.x). Any chance you can confirm the version you are using?

For context, if you use the GraphPhpLeagueAccessTokenProvider and pass it to the createWithAuthenticationProvider method, what ends up happening is that when you make a call to the api the request will be authenticated before the request is made using the configurations passed using the GraphPhpLeagueAuthenticationProvider.

Essentially, on making a request, this method will be called and tokens cached in the InMemoryAccessTokenCache without you having to set the values in cache.

$graphServiceClient = GraphServiceClient::createWithAuthenticationProvider(
	GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
		GraphPhpLeagueAccessTokenProvider::createWithCache(
			$inMemoryCache,
			$tokenRequestContext,
			$scopes
		)
	)
);

$user = $graphServiceClient->me()->get()->wait();

@thorby68 just for clarification, does your application require a signed-in user who's log-in session you are trying to persist?
Or are you using application permissions?

@thorby68 just for clarification, does your application require a signed-in user who's log-in session you are trying to persist? Or are you using application permissions?

No! no users required, no log-in required, just API access.
All we will be doing is listing drive folders, creating drive folder, uploading files. Basically that's it.

In that case you don't need to manage access tokens yourself. you can initialize a GraphServiceClient as shown here and make calls to the Graph.

The SDK will fetch access tokens & refresh them where necessary.

Thanks for raising this @thorby68

To help understand this better, any chance you've been able to come across the docs at this link? https://github.com/microsoftgraph/msgraph-sdk-php/blob/main/docs/Examples.md#access-token-management

I believe the first example would be incorrect as it uses imports and types that do not exist in the latest version of the php sdk(v2.x.x). Any chance you can confirm the version you are using?

For context, if you use the GraphPhpLeagueAccessTokenProvider and pass it to the createWithAuthenticationProvider method, what ends up happening is that when you make a call to the api the request will be authenticated before the request is made using the configurations passed using the GraphPhpLeagueAuthenticationProvider.

Essentially, on making a request, this method will be called and tokens cached in the InMemoryAccessTokenCache without you having to set the values in cache.

$graphServiceClient = GraphServiceClient::createWithAuthenticationProvider(
	GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
		GraphPhpLeagueAccessTokenProvider::createWithCache(
			$inMemoryCache,
			$tokenRequestContext,
			$scopes
		)
	)
);

$user = $graphServiceClient->me()->get()->wait();

Thank you for you help.
You will see in my original post, this is the "exact" method we want to adpot. However, before using this method, we must first populate the cache with a token (plus anything else that is required). I understand that creating the cache may be a one-time effort, the above code will handle things from there, but I don't know the first step of how to populate the cache in the first instance?

In that case you don't need to manage access tokens yourself. you can initialize a GraphServiceClient as shown here and make calls to the Graph.

The SDK will fetch access tokens & refresh them where necessary.

Many thanks, but the method you reference will me I create a new token for every request. I was hoping to use the in memory cache ensuring the token is used when valid and the refresh token is used when the token has expired?

In that case you don't need to manage access tokens yourself. you can initialize a GraphServiceClient as shown here and make calls to the Graph.
The SDK will fetch access tokens & refresh them where necessary.

Many thanks, but the method you reference will me I create a new token for every request. I was hoping to use the in memory cache ensuring the token is used when valid and the refresh token is used when the token has expired?

Each instance of the GraphServiceClient caches tokens and refreshes them only when expired.
In case your scenario can use the same instance of the GraphServiceClient for multiple API calls within the same process, then you should be good to go.

If you'd like to use this in separate processes and cache the tokens yourself you can follow the guidance here to initialize an InMemoryAccessTokenCache & get tokens from the cache to persist externally. To make a future request using the externally stored token, you can pass the token back to the SDK by instantiating a cache here follows. The SDK will refresh the token in the cache if necessary.

Hi Gents, thank you for your support.

I'm not sure what is happenning here because when i try to confirm the token is stored (a one time thing to prove all is working OK) I get an error. I'm using the following getter to get the token:

$accessToken = $inMemoryCache->getTokenWithContext($tokenRequestContext);
		
		echo $accessToken;

The error is returned by the InMemoryAccessTokenCache getter: getTokenWithContext, indicating the inMemoryCache is empty!

public function getTokenWithContext(TokenRequestContext $tokenRequestContext): ?AccessToken {
        if (is_null($tokenRequestContext->getCacheKey())) {
            throw new InvalidArgumentException("Unable to get token using context with a null cache key");
        }
        return $this->getAccessToken($tokenRequestContext->getCacheKey());
    }

As indicated above, the error I'm receiving is "Unable to get token using context with a null cache key". This is returned from the code (in full) below:


	use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
	use Microsoft\Graph\GraphServiceClient;
	use Microsoft\Kiota\Authentication\Cache\InMemoryAccessTokenCache;
	use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAccessTokenProvider;
	use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAuthenticationProvider;

		require vendor/autoload.php';
		
		$tokenRequestContext = new ClientCredentialContext(
			'mytenantid',
			'myclientid',
			'myclientsecret'
		);
		
		$graphServiceClient = new GraphServiceClient($tokenRequestContext);
		
		$scopes = ['https://graph.microsoft.com/.default'];

		$inMemoryCache = new InMemoryAccessTokenCache();

		$graphServiceClient = GraphServiceClient::createWithAuthenticationProvider(
			GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
				GraphPhpLeagueAccessTokenProvider::createWithCache(
					$inMemoryCache,
					$tokenRequestContext,
					$scopes
				)
			)
		);
		$accessToken = $inMemoryCache->getTokenWithContext($tokenRequestContext);
		
		echo $accessToken;

UPDATE
Using Graph Explorer (with my credentails) I can execute
https://graph.microsoft.com/v1.0/me/drive"
and get a valid response

{
     "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives/$entity",
     "@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET me/drive?$select=driveType,owner",
     "createdDateTime": "2023-10-22T00:11:21Z",
     "description": "",
     "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
     "lastModifiedDateTime": "2024-06-27T10:39:41Z",
     "name": "OneDrive",
     "webUrl": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Documents",
     "driveType": "business",
     "createdBy": {
         "user": {
             "displayName": "System Account"
         }
     },
     "lastModifiedBy": {
         "user": {
             "email": "Martin@jobcop.co.uk",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "owner": {
         "user": {
             "email": "Martin@jobcop.co.uk",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "quota": {
         "deleted": 1030849340,
         "remaining": 1097450601323,
         "state": "normal",
         "total": 1099511627776,
         "used": 1030177113
     }
 }

However, when I execcute the equivalent via the SDK,

$result = $graphServiceClient->me()->drive()->get()->wait();

fails at:

public static function createFromDiscriminatorValue(ParseNode $parseNode): ODataError {
         return new ODataError();
}

The full code from the SDK

```

use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Kiota\Authentication\Cache\InMemoryAccessTokenCache;
use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAccessTokenProvider;
use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAuthenticationProvider;

	require APPPATH.'ThirdParty/OnedriveAPI/vendor/autoload.php';
	
	$tokenRequestContext = new ClientCredentialContext(
		'mytenantid',
		'myclientid',
		'myclientsecret'
	);
	
	$inMemoryCache = new InMemoryAccessTokenCache();
	$scopes = ['https://graph.microsoft.com/.default'];
	$graphServiceClient = GraphServiceClient::createWithAuthenticationProvider(
		GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
			GraphPhpLeagueAccessTokenProvider::createWithCache(
				$inMemoryCache,
				$tokenRequestContext,
				$scopes
			)
		)
	);

	$result = $graphServiceClient->me()->drive()->get()->wait();
	echo $result;

@thorby68 to use the /me endpoints, you need a signed-in user. Since your application doesn't need a signed in user, you can replace /me with /user/{user-id} where userId can be the actual ID or a user-principal name of the user whose data you're interested in e.g. username@tenant.com
$graphServiceClient->users()->byUserId("{user-id}")->drive()->get()->wait()

Further reference:

@thorby68 to use the /me endpoints, you need a signed-in user. Since your application doesn't need a signed in user, you can replace /me with /user/{user-id} where userId can be the actual ID or a user-principal name of the user whose data you're interested in e.g. username@tenant.com $graphServiceClient->users()->byUserId("{user-id}")->drive()->get()->wait()

Further reference:

* https://learn.microsoft.com/en-us/graph/api/resources/users?view=graph-rest-1.0

Thank you @Ndiritu that is brilliant, thank you.
However, how would you extend the above into a query like below that is derived from "https://graph.microsoft.com/v1.0/me/drive/root/children" - the SDK tells me root() and children() are not part of the drive request builder

$result = $graphServiceClient->users()->byUserId("my-user-id")->drive()->root()->children()->get()->wait();

Does the php sdk have reference documentation, or how would I translate HTTP examples like "https://graph.microsoft.com/v1.0/me/drive/root/children" into their PHP equivalent?

Lastly does the PHP SDK, provide a response getter and/or getter for every data field returned from the server?

i.e. if I make a query like

$result = $graphServiceClient->drives()->byDriveId('my-drive-id')->get()->wait();

simply using
print_r($result);

prints an object but does not seems to contain the server response, but using the Id getter
echo $result->getId();

returns the drive id. Is there a reponse getter, i.e.
$result->getResponse();

or
$result->getbody();

to present a server response similar to the json respons from a HHTP

{
     "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives/$entity",
     "@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET me/drive?$select=driveType,owner",
     "createdDateTime": "2023-10-22T00:11:21Z",
     "description": "",
     "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
     "lastModifiedDateTime": "2024-06-27T10:39:41Z",
     "name": "OneDrive",
     "webUrl": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Documents",
     "driveType": "business",
     "createdBy": {
         "user": {
             "displayName": "System Account"
         }
     },
     "lastModifiedBy": {
         "user": {
             "email": "Martin@jobcop.co.uk",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "owner": {
         "user": {
             "email": "Martin@jobcop.co.uk",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "quota": {
         "deleted": 1030849340,
         "remaining": 1097450601323,
         "state": "normal",
         "total": 1099511627776,
         "used": 1030177113
     }
 }