Initial Version

This commit is contained in:
root
2025-12-21 10:00:27 -05:00
commit 52ff8a8314
12 changed files with 1118 additions and 0 deletions

157
lib/Provider.php Normal file
View File

@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace KTXM\AuthenticationProviderOidc;
use KTXF\Security\Authentication\AuthenticationProviderAbstract;
use KTXF\Security\Authentication\ProviderContext;
use KTXF\Security\Authentication\ProviderResult;
/**
* OpenID Connect Identity Provider
*
* Implements OIDC authentication flow for SSO.
* Can be used as primary (redirect to IdP) or secondary (step-up authentication).
*/
class Provider extends AuthenticationProviderAbstract
{
public function __construct(
private readonly OidcClient $client,
) { }
// =========================================================================
// Provider Implementation
// =========================================================================
public function type(): string
{
return 'authentication';
}
public function identifier(): string
{
return 'oidc';
}
public function method(): string
{
return self::METHOD_REDIRECT;
}
public function label(): string
{
return 'OpenID Connect';
}
public function description(): string
{
return 'Authenticate using an OpenID Connect identity provider.';
}
public function icon(): string
{
return 'fa-solid fa-circle-o';
}
public function beginRedirect(ProviderContext $context, string $callbackUrl, ?string $returnUrl = null): ProviderResult
{
$config = $context->config;
if (empty($config)) {
return ProviderResult::failed(
ProviderResult::ERROR_INVALID_PROVIDER,
'OIDC provider configuration is missing'
);
}
try {
$authData = $this->client->beginAuth($config, $callbackUrl, $returnUrl);
return ProviderResult::redirect(
$authData['redirect_url'],
[
'state' => $authData['state'],
'nonce' => $authData['nonce'] ?? null,
'return_url' => $returnUrl,
]
);
} catch (\Throwable $e) {
return ProviderResult::failed(
ProviderResult::ERROR_INTERNAL,
'Failed to initiate OIDC authentication: ' . $e->getMessage()
);
}
}
public function completeRedirect(ProviderContext $context, array $params): ProviderResult
{
$config = $context->config;
$expectedState = $context->getMeta('state');
$expectedNonce = $context->getMeta('nonce');
if (empty($config)) {
return ProviderResult::failed(
ProviderResult::ERROR_INVALID_PROVIDER,
'OIDC provider configuration is missing'
);
}
if (empty($expectedState)) {
return ProviderResult::failed(
ProviderResult::ERROR_INVALID_CREDENTIALS,
'Missing expected state for OIDC verification'
);
}
try {
$result = $this->client->completeAuth($params, $config, $expectedState, $expectedNonce);
if (!$result || !isset($result['success']) || !$result['success']) {
return ProviderResult::failed(
ProviderResult::ERROR_INVALID_CREDENTIALS,
$result['error'] ?? 'OIDC authentication failed'
);
}
return ProviderResult::success(
[
'provider' => $this->identifier(),
'subject' => $result['sub'] ?? null,
'email' => $result['email'] ?? null,
'name' => $result['name'] ?? null,
'attributes' => $result['attributes'] ?? [],
],
[
'return_url' => $context->getMeta('return_url'),
]
);
} catch (\Throwable $e) {
return ProviderResult::failed(
ProviderResult::ERROR_INTERNAL,
'Failed to complete OIDC authentication: ' . $e->getMessage()
);
}
}
// =========================================================================
// Attribute Helpers
// =========================================================================
public function getAttributes(array $identity): array
{
return $identity['attributes'] ?? [];
}
public function mapAttributes(array $attributes, array $mapping): array
{
$mapped = [];
foreach ($mapping as $sourceKey => $targetKey) {
if (isset($attributes[$sourceKey])) {
$mapped[$targetKey] = $attributes[$sourceKey];
}
}
return $mapped;
}
}