Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web API Token and Refresh Token #1595

Open
thorby68 opened this issue Oct 7, 2024 · 11 comments
Open

Web API Token and Refresh Token #1595

thorby68 opened this issue Oct 7, 2024 · 11 comments
Assignees

Comments

@thorby68
Copy link

thorby68 commented Oct 7, 2024

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

@thorby68 thorby68 added the status:waiting-for-triage An issue that is yet to be reviewed or assigned label Oct 7, 2024
@andrueastman
Copy link
Member

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

@andrueastman andrueastman added status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close and removed status:waiting-for-triage An issue that is yet to be reviewed or assigned labels Oct 8, 2024
@Ndiritu
Copy link
Contributor

Ndiritu commented Oct 8, 2024

@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
Copy link
Author

thorby68 commented Oct 8, 2024

@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.

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Attention 👋 and removed status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close labels Oct 8, 2024
@Ndiritu
Copy link
Contributor

Ndiritu commented Oct 8, 2024

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.

@thorby68
Copy link
Author

thorby68 commented Oct 8, 2024

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?

@thorby68
Copy link
Author

thorby68 commented Oct 8, 2024

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?

@Ndiritu
Copy link
Contributor

Ndiritu commented Oct 8, 2024

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.

@thorby68
Copy link
Author

thorby68 commented Oct 8, 2024

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;

@Ndiritu Ndiritu self-assigned this Oct 8, 2024
@thorby68
Copy link
Author

thorby68 commented Oct 9, 2024

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": "[email protected]",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "owner": {
         "user": {
             "email": "[email protected]",
             "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;

@Ndiritu
Copy link
Contributor

Ndiritu commented Oct 9, 2024

@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. [email protected]
$graphServiceClient->users()->byUserId("{user-id}")->drive()->get()->wait()

Further reference:

@thorby68
Copy link
Author

thorby68 commented Oct 9, 2024

@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. [email protected] $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": "[email protected]",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "owner": {
         "user": {
             "email": "[email protected]",
             "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
             "displayName": "Martin Thorburn"
         }
     },
     "quota": {
         "deleted": 1030849340,
         "remaining": 1097450601323,
         "state": "normal",
         "total": 1099511627776,
         "used": 1030177113
     }
 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants