tenant = $credentials['tenant']; $this->clientId = $credentials['client_id']; $this->clientSecret = $credentials['client_secret']; } else { throw new \RuntimeException('oauth_credentials settings are not set.'); } } public function getToken(): string { return $this->cache->get(self::CACHE_KEY)?->data ?? $this->fetchToken(); } public function fetchToken(): string { $data = [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'scope' => self::SCOPE, 'grant_type' => self::GRANT_TYPE, ]; $oAuthUrl = str_replace('{tenant}', $this->tenant, self::OAUTH_URL); $body = $this->streamFactory->createStream(http_build_query($data)); $request = $this->serverRequestFactory->createServerRequest('POST', $oAuthUrl) ->withHeader('Content-Type', 'application/x-www-form-urlencoded') ->withBody($body); try { $response = $this->httpClient->sendRequest($request); if (200 !== $response->getStatusCode()) { throw new \RuntimeException('Failed to fetch oauth token from Microsoft: ' . $response->getBody()); } $auth = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $accessToken = $auth['access_token'] ?? null; if (!$accessToken) { throw new \RuntimeException('Received empty access token from Microsoft: ' . $response->getBody()); } $expiresIn = $auth['expires_in'] ?? time() + 300; // Subtracting 60 seconds from the TTL // as a safety margin to certainly not use an expiring token. $expiryTime = $expiresIn - 60; $this->cache->set(self::CACHE_KEY, $accessToken, $expiryTime); return $accessToken; } catch (\Throwable $exception) { $this->logger->alert($exception->getMessage()); } return ''; } }