Closed
Description
The repo has very few examples and no upgrade guide. A few issues here wonder where Microsoft\Graph\Graph
went.
I tried figuring out myself and am pretty sure that this is a breaking change, if not a bug or undocumented behavior.
This code worked on 2.0-RC4
:
<?php
namespace Support\Extensions\Auth\AzureActiveDirectory\Actions;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\GenericProvider;
use Microsoft\Graph\Graph;
use Support\Extensions\Auth\AzureActiveDirectory\Dtos\TenantDto;
class AuthenticateToTenantAction
{
private GenericProvider $oauthClient;
private Graph $graph;
private string $authUrl;
public function __construct(TenantDto $tenantDto)
{
$this->oauthClient = new GenericProvider([
'clientId' => $tenantDto->clientId,
'clientSecret' => $tenantDto->clientSecret,
'redirectUri' => $tenantDto->redirectUri,
'urlAuthorize' => $tenantDto->urlAuthorize,
'urlAccessToken' => $tenantDto->urlAccessToken,
'scopes' => $tenantDto->scopes,
'urlResourceOwnerDetails' => $tenantDto->urlResourceOwnerDetails,
]);
$this->authUrl = $this->oauthClient->getAuthorizationUrl();
$this->graph = new Graph();
}
public function getAuthUrl(): string
{
return $this->authUrl;
}
public function getState(): string
{
return $this->oauthClient->getState();
}
public function getUser(string $authCode)
{
try {
$token = $this->oauthClient->getAccessToken('authorization_code', [
'code' => $authCode,
]);
} catch (IdentityProviderException $e) {
throw new \Exception('IdentityProviderException: '.$e->getMessage().' '.json_encode($e->getResponseBody()));
}
$this->graph->setAccessToken($token->getToken());
$user = $this->graph->createRequest('GET', '/me?$select=displayName,mail')
->execute()->getBody();
return [$token, $user];
}
}
Then I upgraded to 2.14 and tried this (kindly look at the two comments):
<?php
namespace Support\Extensions\Auth\AzureActiveDirectory\Actions;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\GenericProvider;
use Microsoft\Graph\Generated\Users\Item\UserItemRequestBuilderGetRequestConfiguration;
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Kiota\Authentication\Oauth\OnBehalfOfContext;
use Support\Extensions\Auth\AzureActiveDirectory\Dtos\TenantDto;
class AuthenticateToTenantAction
{
private GenericProvider $oauthClient;
private string $authUrl;
public function __construct(private TenantDto $tenantDto)
{
$this->oauthClient = new GenericProvider([
'clientId' => $tenantDto->clientId,
'clientSecret' => $tenantDto->clientSecret,
'redirectUri' => $tenantDto->redirectUri,
'urlAuthorize' => $tenantDto->urlAuthorize,
'urlAccessToken' => $tenantDto->urlAccessToken,
'scopes' => $tenantDto->scopes,
'urlResourceOwnerDetails' => $tenantDto->urlResourceOwnerDetails,
]);
$this->authUrl = $this->oauthClient->getAuthorizationUrl();
}
public function getAuthUrl(): string
{
return $this->authUrl;
}
public function getState(): string
{
return $this->oauthClient->getState();
}
public function getUser(string $authCode)
{
try {
$token = $this->oauthClient->getAccessToken('authorization_code', [
'code' => $authCode,
]);
} catch (IdentityProviderException $e) {
throw new \Exception('IdentityProviderException: '.$e->getMessage().' '.json_encode($e->getResponseBody()));
}
$tokenRequestContext = new OnBehalfOfContext(
tenantId: 'common', # <-- I did not need that in my old code?
clientId: $this->tenantDto->clientId,
clientSecret: $this->tenantDto->clientSecret,
assertion: $token->getToken(),
);
$requestConfiguration = new UserItemRequestBuilderGetRequestConfiguration();
$queryParameters = UserItemRequestBuilderGetRequestConfiguration::createQueryParameters();
$queryParameters->select = ['displayName','mail'];
$requestConfiguration->queryParameters = $queryParameters;
$graphServiceClient = new GraphServiceClient($tokenRequestContext, explode(' ', $this->tenantDto->scopes));
$user = $graphServiceClient->me()->get($requestConfiguration)->wait(); # <-- This throw the exception below
return [$token, $user];
}
}
It gives Assertion failed signature validation
{#2307 ▼ // src/Support/Extensions/Auth/AzureActiveDirectory/Actions/AuthenticateToTenantAction.php:62
-exception:
League\OAuth2\Client\Provider\Exception
\
IdentityProviderException {#2304 ▼
#message: "invalid_grant"
#code: 0
#file: "
/var/www/html/vendor
/league/oauth2-client/
src/Provider/GenericProvider.php"
#line: 222
#response: array:7 [▼
"error" => "invalid_grant"
"error_description" => "
AADSTS50013: Assertion failed signature validation. [Reason - Key was found, but use of the key to verify the signature failed., Thumbprint of key used by client: '1FD9E3E40392B30329860D52171EE3695FA507DC', Found key 'Start=08/18/2024 19:33:23, End=08/18/2029 19:33:23', Please visit the Azure Portal, Graph Explorer or directly use MS Graph to see configured keys for app Id '00000000-0000-0000-0000-000000000000'. Review the documentation at https://docs.microsoft.com/en-us/graph/deployments to determine the corresponding service endpoint and https://docs.microsoft.com/en-us/graph/api/application-get?view=graph-rest-1.0&tabs=http to build a query request URL, such as 'https://graph.microsoft.com/beta/applications/00000000-0000-0000-0000-000000000000']. Trace ID: 779c7e83-1547-45d7-84e9-1dc190a70a00 Correlation ID: 6577dee5-4675-4cac-875d-7faa7929c1ef Timestamp: 2024-09-21 16:34:51Z
◀
"
"error_codes" => array:1 [▶]
"timestamp" => "2024-09-21 16:34:51Z"
"trace_id" => "779c7e83-1547-45d7-84e9-1dc190a70a00"
"correlation_id" => "6577dee5-4675-4cac-875d-7faa7929c1ef"
"error_uri" => "https://login.microsoftonline.com/error?code=50013"
]
Here is the controller consuming `` for both versions:
<?php
namespace Support\Extensions\Auth\AzureActiveDirectory\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Support\Extensions\Auth\AzureActiveDirectory\Actions\AuthenticateToTenantAction;
use Support\Extensions\Auth\AzureActiveDirectory\Actions\FindUserAction;
use Support\Extensions\Auth\AzureActiveDirectory\Actions\PersistTokenAction;
use Support\Extensions\Auth\AzureActiveDirectory\Actions\SignInUserAction;
use Support\Extensions\Auth\AzureActiveDirectory\Dtos\TenantDto;
use Support\Extensions\Auth\AzureActiveDirectory\Models\TenantModel;
class AzureActiveDirectoryAuthController extends Controller
{
public function signin(TenantModel $tenant)
{
$authenticateTenantAction = new AuthenticateToTenantAction(
TenantDto::fromArray($tenant->toArray())
);
session(['oauthState' => $authenticateTenantAction->getState()]);
return redirect()->away($authenticateTenantAction->getAuthUrl());
}
public function callback(Request $request, TenantModel $tenant)
{
$expectedState = $request->session()->pull('oauthState', null);
if (is_null($expectedState)) {
return redirect('/login')->with(['error' => 'No state sent to Microsoft']);
}
$actualState = $request->query('state');
if (is_null($actualState) || $expectedState !== $actualState) {
return redirect('/login')->with(['error' => 'Microsoft returned a different state than expected.']);
}
$authenticateTenantAction = new AuthenticateToTenantAction(
TenantDto::fromArray($tenant->toArray())
);
try {
if (is_null($request->query('code'))) {
return redirect('/login')->with(['error' => 'Microsoft login did not return a code. Did you use your work email?']);
}
[$token, $userResponse] = $authenticateTenantAction->getUser($request->query('code'));
$user = FindUserAction::for($userResponse, $tenant);
SignInUserAction::for($user);
$token = PersistTokenAction::for($user, $token);
} catch (ModelNotFoundException $e) {
report('Azure AD Error: We tried finding this AD user in our users table but got ModelNotFoundException: '.json_encode($userResponse));
return redirect('/login')->with(['error' => 'Your admin should create you in our system first.']);
} catch (IdentityProviderException $e) {
$response = is_string($e->getResponseBody()) ? json_decode($e->getResponseBody(), true) : $e->getResponseBody();
$message = $response['error_description'] ?? $e->getResponseBody();
report("Azure AD Error: $message");
return redirect('/login')->with(['error' => $message]);
} catch (\Exception $e) {
report($e);
return redirect('/login')->with(['error' => $e->getMessage()]);
}
return redirect('/login');
}
}