msgraph-sdk-php icon indicating copy to clipboard operation
msgraph-sdk-php copied to clipboard

access_token from OAuth login: already redeemed

Open penkyhurk opened this issue 3 weeks ago • 0 comments

Hello everyone!

I've successfully implemented a proof of concept for an application with Authorization Code Flow.

Calling the code below by browser, shows expected informations, BUT when one reloads the "page" a "IdentityProviderException" with message "invalid_grant" occures - wich is really missleading IMHO. Digging deeper one finds this "error_description": "AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code ..."

The code below (as a proof of concept) is part of a SaaS application and should handle different taks. In this sceanario above exception is thrown from second try to use the script. As you can see, the "access_token" is not used directly but it seems the SDK uses it internally.

Is there a clean implementation to avoid such exceptions?

I have already a "brute force solution" by redirecting all new calls to the script to 'https://login.microsoftonline.com/'.TENANT_ID.'/oauth2/v2.0/authorize' but this realy, realy ugly and far from a clean solution I think.

What is wrong with this code or is this a "misbehavior" of SDK?

   const TENANT_ID        = '...';
   const CLIENT_ID        = '...';
   const SECRET           = '...';
   const REDIRECT_TO_URI  = 'https://example.com/GraphApplicationTest.php';  // placeholder!!

   const MS_AUTHORIZE_URI = 'https://login.microsoftonline.com/'.TENANT_ID.'/oauth2/v2.0/authorize';
   const MS_TOKEN_URI     = 'https://login.microsoftonline.com/'.TENANT_ID.'/oauth2/v2.0/token';
   const MS_RESSOURCE_URI = 'https://login.microsoftonline.com/'.TENANT_ID;


   $basicScopes    = [ 'https://graph.microsoft.com/.default' ];
   $extendedScopes = [
      'openid',
      'offline_access',
      'User.Read',
      'User.Read.All',
      'User.ReadBasic.All',
       ];

   $oauthGenProvider  = NULL;
   $authCode     = "";
   $accessToken  = NULL;
   $nextToken    = NULL;
   $tokenContext = NULL;


   $oauthGenProvider = new GenericProvider([
   		'clientId'                => CLIENT_ID,
   		'clientSecret'            => SECRET,
   		'redirectUri'             => REDIRECT_TO_URI,
   		'urlAuthorize'            => MS_AUTHORIZE_URI,
   		'urlAccessToken'          => MS_TOKEN_URI,
   		'urlResourceOwnerDetails' => MS_RESSOURCE_URI,
   		'scopes'                  => $basicScopes,
   	],
	);


   // check errors from eventually previous request
   if ( isset( $_GET['error'] ) )
   {
   	echo "<pre>";
   	echo "exiting with error!\n\n";
      echo "error: ".$_GET['error']."\n";
      echo "description:\n       ".$_GET['error_description']."\n";
      exit;
   }

   // if not authenticated goto "login"
	if (!isset($_GET['code']))
	{
		$authorizationUrl = $oauthGenProvider->getAuthorizationUrl();
		header('Location: ' . $authorizationUrl);
		exit;
	}





 	echo "<pre>";
	echo "SDK version is ".GraphConstants::SDK_VERSION."\n";


	$authCode = $_GET['code'];

   // first step to prepare for "GraphServiceClient": create a context of type "AuthorizationCodeContext"
   // Microsoft\Kiota\Authentication\Oauth\AuthorizationCodeContext
   // https://github.com/microsoft/kiota-authentication-phpleague-php/blob/main/src/Oauth/AuthorizationCodeContext.php

   $tokenContext = new AuthorizationCodeContext(
      		TENANT_ID,
      		CLIENT_ID,
      		SECRET,
      		$authCode,
            REDIRECT_TO_URI,
      	);

   $graphClient = new GraphServiceClient($tokenContext, $extendedScopes);

   try
   {

      $users = $graphClient->users()->get()->wait();

      $userList = $users->getValue();  // returns an array!

      // $userList[x] <==> Microsoft\Graph\Generated\Models\User
      //                   https://github.com/microsoftgraph/msgraph-sdk-php/blob/main/src/Generated/Models/User.php

      echo "   user[0] display name:   ".$userList[0]->getDisplayName()."\n";

   }
   catch ( Exception $e )
   {
      if ( $e instanceof \League\OAuth2\Client\Provider\Exception\IdentityProviderException )
      {
         echo "   IdentityProviderException\n";
         echo $e->getResponseBody()["error_description"];
         // AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token.
      }
      else
      {
         echo "   Exception";
      }
      echo $e->getMessage()."\n";
   }

penkyhurk avatar Dec 09 '25 11:12 penkyhurk