microsoftgraph / msgraph-sdk-php

Microsoft Graph Library for PHP.
Other
582 stars 144 forks source link

Upgrade from V1 to V2 - New Authentication-Flow #1490

Open michaelLoeffelmann opened 8 months ago

michaelLoeffelmann commented 8 months ago

I have an SPA (SinlgePageApplication), which can get an ID and an access token via the msal library. These tokens are included in the header with every request to my API. The API-Gateway routes the request to an microservice that uses the msgraph-sdk-php to communicate with Microsoft.

...
$graph = new Graph();
$graph->setAccessToken(ACCESS_TOKEN_FROM_HEADER);
...

How can I use my given access token together with version 2.x? Because the microservice is not accessible from outside the kubernetes cluster, i can't redirect the user through the OAuth process.

uncaught commented 8 months ago

You need 2.3 at least and then create an implementation like this: https://github.com/microsoftgraph/msgraph-sdk-php/issues/1407#issuecomment-2019816707

It's a bit cumbersome at the moment. There is more discussion going on in https://github.com/microsoftgraph/msgraph-sdk-php/issues/1469, too.

dominicgroza72 commented 6 months ago

You need 2.3 at least and then create an implementation like this: #1407 (comment)

It's a bit cumbersome at the moment. There is more discussion going on in #1469, too.

I'm trying to do this via your implementation and i get the following error

the server returned an unexpected status code and no error class is registered for this code 401 {"error":{"code":"InvalidAuthenticationToken","message":"IDX14100: JWT is not well formed, there are no dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format.

class GraphFactory {
    public static function create(string $accessToken): GraphServiceClient {
        $token = new AccessToken([
            'access_token' => $accessToken,
            'expires' => time() + 10, //Our AccessTokenProvider makes sure the token is valid for at least 60 seconds
        ]);

        $tokenRequestContext = new class extends AuthorizationCodeContext {
            public function __construct() {
                //We don't want Microsoft\Graph to request access tokens itself, but all these values may not be empty:
                parent::__construct('x', 'x', 'x', 'x', 'x');
            }

            public function getCacheKey(): ?string {
                return 'ignored'; //this ends up as $identity in AccessTokenCache::getAccessToken(), which we don't use
            }
        };

        $cache = new class($token) implements AccessTokenCache {

            public function __construct(private readonly AccessToken $token) {
            }

            public function getAccessToken(string $identity): ?AccessToken {
                return $this->token;
            }

            public function persistAccessToken(string $identity, AccessToken $accessToken): void {
             Log::warning('Microsoft\Graph trying to persist access token!');
            }
        };

        return GraphServiceClient::createWithAuthenticationProvider(
            GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
                GraphPhpLeagueAccessTokenProvider::createWithCache($cache, $tokenRequestContext)
            )
        );
    }
}

$graphClient =
            GraphFactory::create(MicrosoftToken::first()->access_token);
        $user = $graphClient->me()->get()->wait();
uncaught commented 6 months ago

is that coming from your server? then please post a callstack.

if that error comes from Microsoft, then I doubt the implementation here is at fault bc it doesn't modify the token 🤔 does the same token work with v1?

dominicgroza72 commented 6 months ago

@uncaught i wanted to implement v2 directly I have another problem now, in my callback function i generate the access token using league/genericProvider

$oauthClient = new GenericProvider([
                'clientId' => env('OAUTH_APP_ID'),
                'clientSecret' => env('OAUTH_APP_PASSWORD'),
                'redirectUri' => env('OAUTH_REDIRECT_URI'),
                'urlAuthorize' => env('OAUTH_AUTHORITY') . 'common' . env('OAUTH_AUTHORIZE_ENDPOINT'),
                'urlAccessToken' => env('OAUTH_AUTHORITY') . 'common' . env('OAUTH_TOKEN_ENDPOINT'),
                'urlResourceOwnerDetails' => '',
                'scopes' => env('OAUTH_SCOPES')
            ]);

            $accessToken = $oauthClient->getAccessToken('authorization_code', [
                'code' => $authCode
                ...

                  $token->access_token = $accessToken->getToken();
            $token->refresh_token = $accessToken->getRefreshToken();
            $token->expires_at = **now()->addSeconds($accessToken->getExpires());**

this works for a while, but on the next day when i try to use the same tokens to call graph api like such

   $tokenRequestContext = $this->getTokenRequestContext();
     $cache=  new InMemoryAccessTokenCache(
                    $tokenRequestContext,
                    new AccessToken(
                        [
                            'access_token' => $preivouslyStoredAccessToken,
                            'refresh_token' => $previouslyStoredRefreshToken,
                            'expires' => 1
                        ]
                    )
                );

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

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

      private function getTokenRequestContext()
    {
        return new class extends AuthorizationCodeContext {
            public function __construct()
            {
                //We don't want Microsoft\Graph to request access tokens itself
                //but all these values may not be empty:
                parent::__construct('x', 'x', 'x', 'x', 'x');
            }

            public function getCacheKey(): ?string
            {
                return 'ignored';
                //this ends up as $identity in AccessTokenCache::getAccessToken() which we don't use
            }
        };
    }

i get the following error when calling graphServiceClient https://flareapp.io/share/Lm8KN3A5

uncaught commented 6 months ago

I am not sure, what you are trying to do there. Access tokens usually don't last very long. Like 30min is common. After that you have to use the refresh token to get a new access token.

If you have not already implemented a refresh system, may I suggest you take a step back and let this repo handle it for you, as it was designed to? All you need is to implement the persistence layer then. But that's not what this issue here is about.

kintumiku commented 6 months ago

You need 2.3 at least and then create an implementation like this: #1407 (comment) It's a bit cumbersome at the moment. There is more discussion going on in #1469, too.

I'm trying to do this via your implementation and i get the following error

the server returned an unexpected status code and no error class is registered for this code 401 {"error":{"code":"InvalidAuthenticationToken","message":"IDX14100: JWT is not well formed, there are no dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format.

class GraphFactory {
    public static function create(string $accessToken): GraphServiceClient {
        $token = new AccessToken([
            'access_token' => $accessToken,
            'expires' => time() + 10, //Our AccessTokenProvider makes sure the token is valid for at least 60 seconds
        ]);

        $tokenRequestContext = new class extends AuthorizationCodeContext {
            public function __construct() {
                //We don't want Microsoft\Graph to request access tokens itself, but all these values may not be empty:
                parent::__construct('x', 'x', 'x', 'x', 'x');
            }

            public function getCacheKey(): ?string {
                return 'ignored'; //this ends up as $identity in AccessTokenCache::getAccessToken(), which we don't use
            }
        };

        $cache = new class($token) implements AccessTokenCache {

            public function __construct(private readonly AccessToken $token) {
            }

            public function getAccessToken(string $identity): ?AccessToken {
                return $this->token;
            }

            public function persistAccessToken(string $identity, AccessToken $accessToken): void {
             Log::warning('Microsoft\Graph trying to persist access token!');
            }
        };

        return GraphServiceClient::createWithAuthenticationProvider(
            GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider(
                GraphPhpLeagueAccessTokenProvider::createWithCache($cache, $tokenRequestContext)
            )
        );
    }
}

$graphClient =
            GraphFactory::create(MicrosoftToken::first()->access_token);
        $user = $graphClient->me()->get()->wait();

Hi, how was this issue resolved?