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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@
"vendor/bin/phpunit --no-coverage"
]
}
}
}
17 changes: 13 additions & 4 deletions docs/2-Pre-Registered-Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ You can also customize the client behavior with optional parameters:

```php
use SimpleSAML\OpenID\Codebooks\PkceCodeChallengeMethodEnum;
use SimpleSAML\OpenID\Codebooks\ResponseModesEnum;
use Cicnavi\Oidc\PreRegisteredClient;
use Cicnavi\Oidc\CodeBooks\AuthorizationRequestMethodEnum;

Expand All @@ -50,7 +51,8 @@ $oidcClient = new PreRegisteredClient(
fetchUserinfoClaims: true, // Fetch claims from the userinfo endpoint
maxCacheDuration: new \DateInterval('PT6H'), // Cache max TTL
logger: null, // \Psr\Log\LoggerInterface instance
defaultAuthorizationRequestMethod: AuthorizationRequestMethodEnum::FormPost // Determines the default authorization request method.
defaultAuthorizationRequestMethod: AuthorizationRequestMethodEnum::FormPost, // Determines the default authorization request method.
responseMode: null, // Determines the OIDC response mode (e.g., ResponseModesEnum::Query or ResponseModesEnum::FormPost. Fragment is not supported). Null by default.
);
```

Expand All @@ -61,11 +63,17 @@ login process, you can use the `authorize()` method:

```php
use Cicnavi\Oidc\PreRegisteredClient;
use Cicnavi\Oidc\CodeBooks\AuthorizationRequestMethodEnum;
use SimpleSAML\OpenID\Codebooks\ResponseModesEnum;
/** @var PreRegisteredClient $oidcClient */

// File: authorize.php
try {
$oidcClient->authorize();
// You can also explicitly pass custom authorization request method and response mode:
$oidcClient->authorize(
authorizationRequestMethod: AuthorizationRequestMethodEnum::Query,
responseMode: ResponseModesEnum::Query
);
} catch (\Throwable $exception) {
// In real app log the error, redirect user and show error message.
throw $exception;
Expand All @@ -78,8 +86,9 @@ server will initiate a browser redirection to the `redirect_uri`
which was registered with the client (this is your callback).

On the callback URI, you'll receive authorization `code` and `state`
(if state check is enabled) as GET parameters. To use that
authorization code, you can use the `getUserData()` method.
(if state check is enabled) as GET (for `query` response mode) or POST
(for `form_post` response mode) parameters. The `getUserData()` method
automatically handles both types of callbacks.
This method will validate `state` (if `state` check is enabled) and send
an HTTP request to token endpoint using the provided authorization `code`
to retrieve tokens (access and ID token). After that it will try to
Expand Down
19 changes: 15 additions & 4 deletions docs/3-Federated-Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ use FederatedClient\FederatedClientFactory;
$config = require 'path/to/config.php';
$factory = new FederatedClientFactory($config, $logger, $cache);
$client = $factory->build();

// Direct instantiation with custom response mode:
use SimpleSAML\OpenID\Codebooks\ResponseModesEnum;
$client = new FederatedClient(
entityConfig: $entityConfig,
relyingPartyConfig: $relyingPartyConfig,
responseMode: ResponseModesEnum::FormPost // Optional
);
```

### 2. Initiating Authentication
Expand All @@ -77,19 +85,22 @@ method takes the Entity ID of the OpenID Provider the user wants to log in with.

```php

use SimpleSAML\OpenID\Codebooks\ResponseModesEnum;

public function login(string $opEntityId) {
/** @var \Cicnavi\Oidc\FederatedClient $client */
// This will resolve the trust chain, register the client if needed,
// and initiate the authentication flow.
$client->autoRegisterAndAuthenticate($opEntityId);
// and initiate the authentication flow. You can optionally specify a response mode:
$client->autoRegisterAndAuthenticate($opEntityId, responseMode: ResponseModesEnum::FormPost);
}
```

### 3. Handling the Callback

After the user authenticates at the OP, they are redirected back to your
`redirect_uri`. Use the `getUserData` method to complete the flow and
collect user information.
`redirect_uri` via GET (for query response mode) or POST (for form_post response mode).
Use the `getUserData` method to complete the flow and collect user information.
The client automatically parses and handles both GET and POST requests.

```php

Expand Down
15 changes: 15 additions & 0 deletions src/CodeBooks/AuthorizationRequestMethodEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@

namespace Cicnavi\Oidc\CodeBooks;

/**
* Specifies the method used by the Relying Party (RP)
* to deliver the authorization request TO the OpenID
* Provider (OP).
*
* This is orthogonal to the OIDC 'response_mode' parameter:
* - AuthorizationRequestMethodEnum controls the REQUEST
* delivery: sending it via HTTP GET (Query redirect)
* or via HTTP POST (FormPost auto-submit form).
* - The 'response_mode' parameter (ResponseModesEnum)
* controls the RESPONSE delivery: how the OP returns
* the authorization response back to the RP
* (e.g., via 'query' parameters or via 'form_post'
* POST body).
*/
enum AuthorizationRequestMethodEnum
{
/**
Expand Down
25 changes: 25 additions & 0 deletions src/FederatedClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use SimpleSAML\OpenID\Codebooks\EntityTypesEnum;
use SimpleSAML\OpenID\Codebooks\GrantTypesEnum;
use SimpleSAML\OpenID\Codebooks\HashAlgorithmsEnum;
use SimpleSAML\OpenID\Codebooks\ResponseModesEnum;
use SimpleSAML\OpenID\Codebooks\ResponseTypesEnum;
use SimpleSAML\OpenID\Codebooks\TokenEndpointAuthMethodsEnum;
use SimpleSAML\OpenID\Codebooks\TrustMarkStatusEndpointUsagePolicyEnum;
Expand Down Expand Up @@ -141,9 +142,11 @@ public function __construct(
?RequestDataHandler $requestDataHandler = null,
// phpcs:ignore
protected readonly AuthorizationRequestMethodEnum $defaultAuthorizationRequestMethod = AuthorizationRequestMethodEnum::FormPost,
protected readonly ?ResponseModesEnum $responseMode = null,
int $maxDiscoveryDepth = 10,
?EntityCollectionStoreInterface $entityCollectionStore = null,
) {
$this->validateResponseMode($this->responseMode);
$this->cache = $cache ?? new FileCache('ofacpc-' . md5($this->entityConfig->getEntityId()));
$this->signatureKeyPairFactory = $signatureKeyPairFactory ?? new SignatureKeyPairFactory($this->jwk);
$this->signatureKeyPairBagFactory = $signatureKeyPairBagFactory ?? new SignatureKeyPairBagFactory(
Expand Down Expand Up @@ -347,6 +350,10 @@ public function buildEntityStatement(): EntityStatement
$rpMetadata[ClaimsEnum::RequestObjectSigningAlgValuesSupported->value] =
$this->relyingPartyConfig->getConnectSignatureKeyPairBag()->getAllAlgorithmNamesUnique();
$rpMetadata[ClaimsEnum::Scope->value] = $this->relyingPartyConfig->getScopeBag()->toString();
$rpMetadata[ClaimsEnum::ResponseModesSupported->value] = [
ResponseModesEnum::Query->value,
ResponseModesEnum::FormPost->value,
];
if ($this->includeSoftwareId) {
$rpMetadata[ClaimsEnum::SoftwareId->value] = 'https://github.com/cicnavi/oidc-client-php';
}
Expand Down Expand Up @@ -512,8 +519,11 @@ public function autoRegisterAndAuthenticate(
?ResponseInterface $response = null,
?string $clientRedirectUri = null,
?AuthorizationRequestMethodEnum $authorizationRequestMethod = null,
?ResponseModesEnum $responseMode = null,
): ?ResponseInterface {
$authorizationRequestMethod ??= $this->defaultAuthorizationRequestMethod;
$responseMode ??= $this->responseMode;
$this->validateResponseMode($responseMode);
$trustAnchorBag = $this->entityConfig->getTrustAnchorBag();
if ($specificTrustAnchors instanceof TrustAnchorConfigBag) {
$trustAnchorBag = $trustAnchorBag->getInCommonWith($specificTrustAnchors);
Expand Down Expand Up @@ -685,6 +695,7 @@ public function autoRegisterAndAuthenticate(
ParamsEnum::ResponseType->value => ResponseTypesEnum::Code->value,
ParamsEnum::RedirectUri->value => $clientRedirectUri,
ParamsEnum::Scope->value => $scope,
ParamsEnum::ResponseMode->value => $responseMode?->value,
ParamsEnum::State->value => $state,
ParamsEnum::Nonce->value => $nonce,
ParamsEnum::CodeChallenge->value => $pkceCodeChallenge,
Expand Down Expand Up @@ -724,6 +735,7 @@ public function autoRegisterAndAuthenticate(
ParamsEnum::ResponseType->value => ResponseTypesEnum::Code->value,
ParamsEnum::ClientId->value => $this->entityConfig->getEntityId(),
ParamsEnum::RedirectUri->value => $clientRedirectUri,
ParamsEnum::ResponseMode->value => $responseMode?->value,
]);

$this->logger?->debug(
Expand Down Expand Up @@ -936,4 +948,17 @@ public function discoverEntities(

return $entities;
}

/**
* @throws OidcClientException
*/
protected function validateResponseMode(?ResponseModesEnum $responseMode): void
{
if ($responseMode === ResponseModesEnum::Fragment) {
throw new OidcClientException(
"The 'fragment' response mode is not supported because URLs with fragments are " .
"not sent to the server and cannot be handled by a server-side PHP client."
);
}
}
}
22 changes: 22 additions & 0 deletions src/PreRegisteredClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use SimpleSAML\OpenID\Codebooks\ClientAuthenticationMethodsEnum;
use SimpleSAML\OpenID\Codebooks\ParamsEnum;
use SimpleSAML\OpenID\Codebooks\PkceCodeChallengeMethodEnum;
use SimpleSAML\OpenID\Codebooks\ResponseModesEnum;
use SimpleSAML\OpenID\Codebooks\ResponseTypesEnum;
use SimpleSAML\OpenID\Core;
use SimpleSAML\OpenID\Exceptions\InvalidValueException;
Expand Down Expand Up @@ -127,8 +128,11 @@ public function __construct(
protected readonly DateInterval $maxCacheDuration = new DateInterval('PT6H'),
// phpcs:ignore
protected readonly AuthorizationRequestMethodEnum $defaultAuthorizationRequestMethod = AuthorizationRequestMethodEnum::FormPost,
protected readonly ?ResponseModesEnum $responseMode = null,
?RequestDataHandler $requestDataHandler = null,
) {
$this->validateResponseMode($this->responseMode);

$this->cache = $cache ?? new FileCache('oprcpc-' . md5($this->clientId));

$this->validateCache();
Expand Down Expand Up @@ -195,8 +199,12 @@ protected function validateCache(): void
public function authorize(
?AuthorizationRequestMethodEnum $authorizationRequestMethod = null,
?ResponseInterface $response = null,
?ResponseModesEnum $responseMode = null,
): ?ResponseInterface {
$authorizationRequestMethod ??= $this->defaultAuthorizationRequestMethod;
$responseMode ??= $this->responseMode;

$this->validateResponseMode($responseMode);

$state = $this->useState ? $this->requestDataHandler->getState() : null;
$nonce = $this->useNonce ? $this->requestDataHandler->getNonce() : null;
Expand All @@ -214,6 +222,7 @@ public function authorize(
ParamsEnum::ClientId->value => $this->clientId,
ParamsEnum::RedirectUri->value => $this->redirectUri,
ParamsEnum::Scope->value => $this->scope,
ParamsEnum::ResponseMode->value => $responseMode?->value,

ParamsEnum::State->value => $state,
ParamsEnum::Nonce->value => $nonce,
Expand Down Expand Up @@ -312,4 +321,17 @@ public function reinitializeCache(): void
$this->cache->clear();
$this->cache->set(self::CACHE_KEY_OP_CONFIGURATION_URL, $this->opConfigurationUrl);
}

/**
* @throws OidcClientException
*/
protected function validateResponseMode(?ResponseModesEnum $responseMode): void
{
if ($responseMode === ResponseModesEnum::Fragment) {
throw new OidcClientException(
"The 'fragment' response mode is not supported because URLs with fragments are " .
"not sent to the server and cannot be handled by a server-side PHP client."
);
}
}
}
7 changes: 6 additions & 1 deletion src/Protocol/RequestDataHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,12 @@ public function validateAuthorizationCallbackResponse(
?ServerRequestInterface $request = null,
bool $useState = true,
): array {
$params = $request?->getQueryParams() ?? $_GET;
$queryParams = $request?->getQueryParams() ?? $_GET;
$parsedBody = $request?->getParsedBody() ?? $_POST;
$params = array_merge(
$queryParams,
is_array($parsedBody) ? $parsedBody : []
);

$error = $params[ParamsEnum::Error->value] ?? null;
$errorDescription = $params[ParamsEnum::ErrorDescription->value] ?? null;
Expand Down
Loading
Loading