ExternalJwtAuthMiddleware.php 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. <?php
  2. namespace Middlewares;
  3. use Libs\ResponseLib;
  4. use Psr\Http\Message\ServerRequestInterface;
  5. class ExternalJwtAuthMiddleware
  6. {
  7. private string $verifyUrl;
  8. private int $timeoutSeconds;
  9. public function __construct(string $verifyUrl)
  10. {
  11. $this->verifyUrl = $verifyUrl;
  12. $this->timeoutSeconds = (int)($_ENV['EXTERNAL_AUTH_TIMEOUT'] ?? 5);
  13. }
  14. public function __invoke(ServerRequestInterface $request, callable $next)
  15. {
  16. // Require Authorization: Bearer <token>
  17. $authHeader = $request->getHeaderLine('Authorization');
  18. if (empty($authHeader) || !preg_match('/Bearer\s+(.*)/', $authHeader, $matches)) {
  19. return ResponseLib::sendFail('Unauthorized: Missing or invalid Authorization header', [], 'E_VALIDATE')->withStatus(401);
  20. }
  21. // Call external service
  22. [$ok, $payload, $status, $err] = $this->callExternalVerify($this->verifyUrl, $authHeader);
  23. if (!$ok) {
  24. $message = 'Unauthorized';
  25. if ($status >= 500 || $err) {
  26. $message = 'Auth service error';
  27. }
  28. return ResponseLib::sendFail($message, ['status' => $status, 'error' => $err], 'E_VALIDATE')->withStatus(401);
  29. }
  30. // Attach attributes from response if present
  31. if (is_array($payload)) {
  32. if (isset($payload['sub'])) {
  33. $request = $request->withAttribute('api_user_id', $payload['sub']);
  34. }
  35. if (isset($payload['username'])) {
  36. $request = $request->withAttribute('api_user', $payload['username']);
  37. }
  38. }
  39. $request = $request->withAttribute('external_auth', true);
  40. return $next($request);
  41. }
  42. private function callExternalVerify(string $url, string $authorization): array
  43. {
  44. $ch = curl_init($url);
  45. curl_setopt_array($ch, [
  46. CURLOPT_RETURNTRANSFER => true,
  47. CURLOPT_HEADER => true,
  48. CURLOPT_NOBODY => false,
  49. CURLOPT_CUSTOMREQUEST => 'GET',
  50. CURLOPT_HTTPHEADER => [
  51. 'Authorization: ' . $authorization,
  52. 'Accept: application/json'
  53. ],
  54. CURLOPT_TIMEOUT => $this->timeoutSeconds,
  55. CURLOPT_CONNECTTIMEOUT => min(2, $this->timeoutSeconds),
  56. ]);
  57. $response = curl_exec($ch);
  58. if ($response === false) {
  59. $err = curl_error($ch);
  60. curl_close($ch);
  61. return [false, null, 0, $err];
  62. }
  63. $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  64. $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
  65. $body = substr($response, $headerSize);
  66. curl_close($ch);
  67. $ok = ($status >= 200 && $status < 300);
  68. $payload = null;
  69. if ($body !== '') {
  70. $decoded = json_decode($body, true);
  71. if (json_last_error() === JSON_ERROR_NONE) {
  72. $payload = $decoded;
  73. } else {
  74. $payload = $body;
  75. }
  76. }
  77. return [$ok, $payload, $status, null];
  78. }
  79. }