Add OIDC discovery, JWKS validation, Socialite integration, OAuth identity linking, and configurable registration policy for password and SSO signups. Update related settings/profile UI and tests.
73 lines
2.5 KiB
PHP
73 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Auth\Oidc;
|
|
|
|
use App\Auth\Oidc\Exceptions\OidcDiscoveryException;
|
|
use App\Auth\Oidc\Exceptions\OidcJwksException;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Throwable;
|
|
|
|
class OidcDiscoveryService
|
|
{
|
|
public function discover(string $issuerUrl): OidcDiscoveryDocument
|
|
{
|
|
$issuerUrl = rtrim($issuerUrl, '/');
|
|
$cacheKey = 'oidc:discovery:'.hash('sha256', $issuerUrl);
|
|
|
|
$payload = Cache::remember($cacheKey, 3600, function () use ($issuerUrl): array {
|
|
$url = $issuerUrl.'/.well-known/openid-configuration';
|
|
|
|
try {
|
|
$response = Http::timeout(5)->connectTimeout(3)->acceptJson()->get($url);
|
|
} catch (Throwable $e) {
|
|
throw new OidcDiscoveryException("Failed to fetch discovery document: {$e->getMessage()}", previous: $e);
|
|
}
|
|
|
|
if ($response->failed()) {
|
|
throw new OidcDiscoveryException("Discovery endpoint returned HTTP {$response->status()}");
|
|
}
|
|
|
|
$json = $response->json();
|
|
if (! is_array($json) || $json === []) {
|
|
throw new OidcDiscoveryException('Discovery endpoint returned invalid JSON.');
|
|
}
|
|
|
|
return $json;
|
|
});
|
|
|
|
$discovery = OidcDiscoveryDocument::fromArray($payload);
|
|
if (rtrim($discovery->issuer, '/') !== $issuerUrl) {
|
|
throw new OidcDiscoveryException('Discovery issuer does not match the configured issuer URL.');
|
|
}
|
|
|
|
return $discovery;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function jwks(string $jwksUri): array
|
|
{
|
|
$cacheKey = 'oidc:jwks:'.hash('sha256', $jwksUri);
|
|
|
|
return Cache::remember($cacheKey, 21600, function () use ($jwksUri): array {
|
|
try {
|
|
$response = Http::timeout(5)->connectTimeout(3)->acceptJson()->get($jwksUri);
|
|
} catch (Throwable $e) {
|
|
throw new OidcJwksException("Failed to fetch JWKS: {$e->getMessage()}", previous: $e);
|
|
}
|
|
|
|
if ($response->failed()) {
|
|
throw new OidcJwksException("JWKS endpoint returned HTTP {$response->status()}");
|
|
}
|
|
|
|
$json = $response->json();
|
|
if (! is_array($json) || ! is_array($json['keys'] ?? null)) {
|
|
throw new OidcJwksException("JWKS endpoint returned an invalid payload without 'keys'.");
|
|
}
|
|
|
|
return $json;
|
|
});
|
|
}
|
|
}
|