Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

All Notable changes to `digipolisgent/api-client` package.

## [4.3.0]

### Added

* Added API key authentication using the `apiKey` and `applicationId` headers.
* Added `ApiKeyConfiguration` and `LegacyConfiguration`, with a shared
`ClientConfigurationInterface` for endpoint, version, and timeout settings.
* Added a v3 to v4.3 upgrade guide and offline authentication compatibility tests.

### Changed

* Made the client token cache optional for API key and legacy configurations;
OIDC still requires it. Existing OIDC configuration signatures, interface
methods, protected property types, and token-provider behavior are preserved.

## [4.2.0]

### Changed
Expand Down Expand Up @@ -117,6 +132,7 @@ This includes:
* Interfaces to create services in client packages.

[Unreleased]: https://github.com/digipolisgent/php_package_dg-api-client/compare/master...develop
[4.3.0]: https://github.com/district09/php_package_dg-api-client/compare/4.2.0...4.3.0
[4.2.0]: https://github.com/digipolisgent/php_package_dg-api-client/compare/4.1.0...4.2.0
[4.1.0]: https://github.com/digipolisgent/php_package_dg-api-client/compare/4.0.0...4.1.0
[4.0.0]: https://github.com/digipolisgent/php_package_dg-api-client/compare/3.0.1...4.0.0
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ See the examples of service packages how to use this package:
* [gent/services/openinghours](https://github.com/StadGent/php_package_services-opening-hours)
: Service to access the Opening Hours API and wrap the responses in value objects.

See [Upgrading from v3 to v4.3](UPGRADE.md) for configuration examples
and migration steps. Existing v4 OIDC consumers
can upgrade to v4.3 without configuration changes.

## Change log

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
Expand Down
122 changes: 122 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Upgrading from v3 to v4.3

Version 4.3 adds API key headers and a legacy configuration path alongside
OIDC. Existing v4 OIDC consumers need no code or configuration changes.

## Composer requirements

Update the API client requirement in your library or application to:

```json
"digipolisgent/api-client": "^4.3"
```

Do not use `^4.0`: the API key and legacy configuration types require 4.3.
Ensure dependent libraries also accept `^4.3`; a remaining `^3.0` requirement
will prevent Composer from resolving the upgrade.

## What v3 actually did

In v3.0.1, `Configuration` accepted an endpoint URI and options. The base client
added Content-Length but did not authenticate requests. Consumers supplied
API key headers, Guzzle authentication, or their own Authorization headers.
Version 4.0 replaced this configuration with OIDC and required a token cache.
Version 4.3 provides explicit configuration types for all three paths.

## API key headers

Use the new configuration when the service authenticates with the `apiKey`
and `applicationId` headers:

```php
use DigipolisGent\API\Client\Configuration\ApiKeyConfiguration;

$configuration = new ApiKeyConfiguration(
'https://api.example',
'your-api-key',
'your-application-id',
['version' => 3, 'timeout' => 10]
);

// Your concrete client extends AbstractClient. No token cache is needed.
$client = new YourClient($guzzle, $configuration);
```

The base client injects these two headers for each request. Remove consumer
code that injects the same headers. Header names are fixed; this is not a
configuration for arbitrary authentication header names.

## Retaining authentication managed by the consumer

Use `LegacyConfiguration` to retain v3 behavior when the consumer handles its
own authentication. It adds no authentication headers and preserves headers
already present on the request.

Before, in v3:

```php
use DigipolisGent\API\Client\Configuration\Configuration as BaseConfiguration;
use DigipolisGent\API\Client\Configuration\ConfigurationInterface as BaseConfigurationInterface;

interface ServiceConfigurationInterface extends BaseConfigurationInterface
{
public function username(): string;
}

class ServiceConfiguration extends BaseConfiguration implements ServiceConfigurationInterface
{
// Existing constructor calls parent::__construct($endpointUri, $options).
// Existing username() implementation is retained.
}
```

After, in v4.3, change only the base imports for this legacy path:

```php
use DigipolisGent\API\Client\Configuration\LegacyConfiguration as BaseConfiguration;
use DigipolisGent\API\Client\Configuration\ClientConfigurationInterface as BaseConfigurationInterface;
```

Retain the consumer's constructor, authentication manager, and header logic.
`LegacyConfiguration($endpointUri, $options)` keeps the v3 defaults: version
`1`, timeout `20`, and ignored unknown options. The base client accepts this
configuration without a token cache.

Update any additional type references that previously accepted the old base
`ConfigurationInterface` to the shared `ClientConfigurationInterface`, or to
your own service configuration interface when service-specific getters are
required. The old name now continues to represent OIDC configuration.

For a service adopting API key headers instead, extend `ApiKeyConfiguration`
and `ApiKeyConfigurationInterface`. Pass endpoint, API key, application ID,
and options to the parent constructor. Retain old getter names as wrappers if
callers depend on them.

## OIDC: unchanged for existing v4 consumers

Keep the existing configuration and cache argument:

```php
use DigipolisGent\API\Client\Configuration\Configuration;

$configuration = new Configuration(
$endpointUri,
$authEndpointUri,
$clientId,
$clientSecret,
$scope,
$options
);
$client = new YourClient($guzzle, $configuration, $tokenCache);
```

OIDC still injects Bearer authentication using the current token provider and
cache behavior. Custom implementations of `ConfigurationInterface` remain
supported, with no new required methods. Omitting the cache for OIDC throws
an `InvalidArgumentException`; there is no automatic authentication fallback.

For existing OIDC subclasses, the protected `$configuration` and
`$tokenProvider` properties retain their original types and initialization.
New API key and legacy subclasses should access the shared protected
`$clientConfiguration` property instead. `$configuration` and `$tokenProvider`
are initialized only for OIDC configurations.
53 changes: 42 additions & 11 deletions src/Client/AbstractClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace DigipolisGent\API\Client;

use DigipolisGent\API\Client\Configuration\ApiKeyConfigurationInterface;
use DigipolisGent\API\Client\Configuration\ClientConfigurationInterface;
use DigipolisGent\API\Client\Configuration\ConfigurationInterface;
use DigipolisGent\API\Client\Exception\HandlerNotFound;
use DigipolisGent\API\Client\Handler\HandlerInterface;
Expand All @@ -17,6 +19,7 @@
use GuzzleHttp\Exception\ClientException;
use Psr\Http\Message\RequestInterface;
use Psr\SimpleCache\CacheInterface;
use InvalidArgumentException;

/**
* Abstract implementation of the service client.
Expand Down Expand Up @@ -48,6 +51,13 @@ abstract class AbstractClient implements ClientInterface, LoggableInterface
*/
protected ConfigurationInterface $configuration;

/**
* Shared configuration for all authentication methods.
*
* @var \DigipolisGent\API\Client\Configuration\ClientConfigurationInterface
*/
protected ClientConfigurationInterface $clientConfiguration;

/**
* The OIDC token provider.
*
Expand All @@ -60,17 +70,28 @@ abstract class AbstractClient implements ClientInterface, LoggableInterface
*
* @param \GuzzleHttp\ClientInterface $guzzle
* The Guzzle HTTP client.
* @param \DigipolisGent\API\Client\Configuration\ConfigurationInterface $configuration
* @param \DigipolisGent\API\Client\Configuration\ClientConfigurationInterface $configuration
* The client configuration object.
* @param \Psr\SimpleCache\CacheInterface $cache
* Cache used for auth Bearer tokens. Not that this is not for API responses.
* @param \Psr\SimpleCache\CacheInterface|null $cache
* Cache required for OIDC Bearer tokens, not API responses.
*/
public function __construct(
GuzzleClientInterface $guzzle,
ConfigurationInterface $configuration,
CacheInterface $cache,
ClientConfigurationInterface $configuration,
?CacheInterface $cache = null,
) {
$this->guzzle = $guzzle;
$this->clientConfiguration = $configuration;

if (!$configuration instanceof ConfigurationInterface) {
return;
}

if ($cache === null) {
throw new InvalidArgumentException('A token cache is required for OIDC authentication.');
}

// Keep the protected OIDC properties compatible with existing subclasses.
$this->configuration = $configuration;
$this->tokenProvider = new OidcTokenProvider(
$configuration->getAuthUri(),
Expand Down Expand Up @@ -116,15 +137,25 @@ public function send(RequestInterface $request): ResponseInterface
*/
protected function injectHeaders(RequestInterface $request): RequestInterface
{
return $request
->withHeader(
'Content-Length',
(string) strlen((string) $request->getBody())
)
->withHeader(
$request = $request->withHeader(
'Content-Length',
(string) strlen((string) $request->getBody())
);

if ($this->clientConfiguration instanceof ConfigurationInterface) {
return $request->withHeader(
'Authorization',
'Bearer ' . $this->tokenProvider->getAccessToken()
);
}

if ($this->clientConfiguration instanceof ApiKeyConfigurationInterface) {
return $request
->withHeader('apiKey', $this->clientConfiguration->getApiKey())
->withHeader('applicationId', $this->clientConfiguration->getApplicationId());
}

return $request;
}

/**
Expand Down
60 changes: 60 additions & 0 deletions src/Client/Configuration/ApiKeyConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace DigipolisGent\API\Client\Configuration;

/**
* Configuration for authentication using API key headers.
*/
class ApiKeyConfiguration extends LegacyConfiguration implements ApiKeyConfigurationInterface
{
/**
* The API key.
*
* @var string
*/
protected string $apiKey;

/**
* The application ID.
*
* @var string
*/
protected string $applicationId;

/**
* Create configuration using the apiKey and applicationId headers.
*
* @param string $endpointUri
* The endpoint URI.
* @param string $apiKey
* The API key.
* @param string $applicationId
* The application ID.
* @param array $options
* The client extra options.
*/
public function __construct(string $endpointUri, string $apiKey, string $applicationId, array $options = [])
{
parent::__construct($endpointUri, $options);
$this->apiKey = $apiKey;
$this->applicationId = $applicationId;
}

/**
* @inheritDoc
*/
public function getApiKey(): string
{
return $this->apiKey;
}

/**
* @inheritDoc
*/
public function getApplicationId(): string
{
return $this->applicationId;
}
}
25 changes: 25 additions & 0 deletions src/Client/Configuration/ApiKeyConfigurationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace DigipolisGent\API\Client\Configuration;

/**
* Configuration for authentication using API key headers.
*/
interface ApiKeyConfigurationInterface extends ClientConfigurationInterface
{
/**
* Get the API key.
*
* @return string
*/
public function getApiKey(): string;

/**
* Get the application ID.
*
* @return string
*/
public function getApplicationId(): string;
}
32 changes: 32 additions & 0 deletions src/Client/Configuration/ClientConfigurationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace DigipolisGent\API\Client\Configuration;

/**
* Configuration used to create the client.
*/
interface ClientConfigurationInterface
{
/**
* Get the endpoint URI.
*
* @return string
*/
public function getUri(): string;

/**
* Get the service version number to use.
*
* @return string
*/
public function getVersion(): string;

/**
* Get the timeout value.
*
* @return int
*/
public function getTimeout(): int;
}
2 changes: 1 addition & 1 deletion src/Client/Configuration/ConfigurationInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* Configuration used to create the client.
*/
interface ConfigurationInterface
interface ConfigurationInterface extends ClientConfigurationInterface
{
/**
* Get the endpoint URI.
Expand Down
Loading
Loading