microsoftgraph / microsoftgraph/msgraph-sdk-php

access_token from OAuth login: already redeemed

Open
#1,749 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
669
Forks
150
Avg merge
15h 21m
Merged PRs (30d)
3

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the OAuth flow around GenericProvider, AuthorizationCodeContext, and GraphServiceClient, then reproduce the failure by reloading the callback URL. Compare the first and second requests and determine whether the SDK should handle the redeemed authorization code differently; done should include a clear expected behavior or documented usage path.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
api, authentication
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.