Skip to content

Breaking changes from 2.0-RC4 to 2.14: Assertion failed signature validation #1585

Closed
@knobel-dk

Description

@knobel-dk

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');
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions