From edc6e6486b1c4aeb77331ba7e3c4991e82e10bdd Mon Sep 17 00:00:00 2001 From: gabriellina640 Date: Tue, 19 May 2026 16:24:51 -0500 Subject: [PATCH] funcional a parte de token --- .env.example | 7 +++--- README.md | 13 ++-------- app/Http/Middleware/JwtAuthMiddleware.php | 29 ++++++++--------------- config/jwt.php | 1 - routes/web.php | 2 ++ tests/Feature/GameRankingApiTest.php | 21 +++++++++++----- 6 files changed, 32 insertions(+), 41 deletions(-) diff --git a/.env.example b/.env.example index 8943d78..51214f6 100644 --- a/.env.example +++ b/.env.example @@ -54,11 +54,10 @@ VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" -JWT_ISSUER= -JWT_AUDIENCE= -JWT_PUBLIC_KEY_PEM= +JWT_ISSUER=https://sistema-distribuido-trabalho-faculd.vercel.app +JWT_AUDIENCE=internal-apis +JWT_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAEAXf0r0pF9jsaUb+T5ue\nm/6lJby0lLqNstP4bpXg4izqzVl8ad9gM/mOS+1M8U204/CMBowC2XFVKQITI78y\n3o1KrlyUpmTfZrrDHidCII53v3E/N6Vou4hEV5xLQhuE61sXB4bwDpr+JgAq17IV\nTfUR+ePFY6xmPCimTuGTXNPOJprkYlV1jEYzMHvtk6FSV39eZDp2GM3wnGYk95ib\n5fFd+xRB8kUdrtub5Cif/ayyF2vsmgsjN41d2qOw6MFsNsXsOXVcrCE/0GvvW5C8\nRTPCifEPbHJ/Du7ye1yDjHDyjYXnnoZ3cOg6VIa12OnlBRfL6sJBT6VCvIbzQN1z\n7QIDAQAB\n-----END PUBLIC KEY-----" JWT_TOKEN= -JWT_ALLOW_ANY_TOKEN=false # Railway production example: # APP_ENV=production diff --git a/README.md b/README.md index 8b3fa80..d38ca51 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,9 @@ Principais variáveis usadas pelo projeto: | `APP_DEBUG` | Ativa ou desativa debug | | `APP_URL` | URL base da aplicação | | `DB_CONNECTION` | Driver do banco, atualmente `sqlite` | -| `JWT_ISSUER` | Emissor esperado no token JWT | -| `JWT_AUDIENCE` | Audiência esperada no token JWT | +| `JWT_ISSUER` | Emissor esperado no token JWT: `https://sistema-distribuido-trabalho-faculd.vercel.app` | +| `JWT_AUDIENCE` | Audiência esperada no token JWT: `internal-apis` | | `JWT_PUBLIC_KEY_PEM` | Chave pública usada para validar JWT RS256 | -| `JWT_ALLOW_ANY_TOKEN` | Quando `true`, aceita qualquer Bearer token. Use apenas para integração/demo | | `SCRIBE_AUTH_KEY` | Token usado apenas para gerar exemplos 200 na documentação | --- @@ -172,14 +171,6 @@ O token precisa: Sem o header `Authorization`, a API retorna `401`. -Para ambientes de demonstração ou integração inicial com o frontend, é possível configurar: - -```env -JWT_ALLOW_ANY_TOKEN=true -``` - -Nesse modo, a API aceita qualquer valor enviado como `Bearer token`. Para produção, mantenha `JWT_ALLOW_ANY_TOKEN=false` e use a validação JWT completa. - --- ## Executando o Projeto diff --git a/app/Http/Middleware/JwtAuthMiddleware.php b/app/Http/Middleware/JwtAuthMiddleware.php index 8d25c2e..fb158d5 100644 --- a/app/Http/Middleware/JwtAuthMiddleware.php +++ b/app/Http/Middleware/JwtAuthMiddleware.php @@ -22,15 +22,6 @@ class JwtAuthMiddleware $token = $matches[1]; - if (config('jwt.allow_any_token')) { - $request->attributes->set('auth', [ - 'id' => $this->subjectFromUnverifiedToken($token), - 'token' => $token - ]); - - return $next($request); - } - [$header, $payload, $signature] = $this->decodeToken($token); if (($header['alg'] ?? null) !== 'RS256') { @@ -40,7 +31,7 @@ class JwtAuthMiddleware if ( !$this->signatureIsValid($token, $signature) || ($payload['iss'] ?? null) !== config('jwt.issuer') || - ($payload['aud'] ?? null) !== config('jwt.audience') || + !$this->audienceIsValid($payload['aud'] ?? null) || empty($payload['sub']) || $this->tokenIsExpired($payload) ) { @@ -123,19 +114,19 @@ class JwtAuthMiddleware return time() >= (int) $payload['exp']; } - private function subjectFromUnverifiedToken(string $token): string + private function audienceIsValid(mixed $audience): bool { - $parts = explode('.', $token); + $expectedAudience = config('jwt.audience'); - if (count($parts) !== 3) { - return 'external-consumer'; + if (is_string($audience)) { + return $audience === $expectedAudience; } - try { - $payload = $this->base64UrlDecodeJson($parts[1]); - return (string) ($payload['sub'] ?? 'external-consumer'); - } catch (\Exception $e) { - return 'external-consumer'; + if (is_array($audience)) { + return in_array($expectedAudience, $audience, true); } + + return false; } + } diff --git a/config/jwt.php b/config/jwt.php index 98648be..b4f8547 100644 --- a/config/jwt.php +++ b/config/jwt.php @@ -4,5 +4,4 @@ return [ 'issuer' => env('JWT_ISSUER'), 'audience' => env('JWT_AUDIENCE'), 'public_key' => env('JWT_PUBLIC_KEY_PEM'), - 'allow_any_token' => env('JWT_ALLOW_ANY_TOKEN', false), ]; diff --git a/routes/web.php b/routes/web.php index 9c7becb..d2998c6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,6 +17,8 @@ Route::get('/', function () { return view('welcome'); }); +Route::redirect('/games/most-played', '/api/v1/games/most-played'); + Route::get('/health', function () { return response('ok', 200); }); diff --git a/tests/Feature/GameRankingApiTest.php b/tests/Feature/GameRankingApiTest.php index 6f446ba..4aa47f5 100644 --- a/tests/Feature/GameRankingApiTest.php +++ b/tests/Feature/GameRankingApiTest.php @@ -11,6 +11,7 @@ class GameRankingApiTest extends TestCase use RefreshDatabase; private string $jwt; + private string $privateKey; protected function setUp(): void { @@ -101,16 +102,23 @@ class GameRankingApiTest extends TestCase ->assertJson(['userId' => 'consumer-project']); } - public function test_can_accept_any_bearer_token_when_enabled_for_demo_integration(): void + public function test_accepts_token_with_audience_array_containing_expected_audience(): void { - config(['jwt.allow_any_token' => true]); + $this->jwt = $this->makeJwt($this->privateKey, ['other-api', 'ranking-api']); - $this->withHeader('Authorization', 'Bearer token-do-front') - ->getJson('/api/v1/rankings/weekly') + $this->getJsonWithJwt('/api/v1/games/most-played') ->assertOk() ->assertJsonCount(10); } + public function test_rejects_generic_bearer_token(): void + { + $this->withHeader('Authorization', 'Bearer token-do-front') + ->getJson('/api/v1/rankings/weekly') + ->assertUnauthorized() + ->assertJson(['message' => 'Invalid or expired token']); + } + private function getJsonWithJwt(string $uri) { return $this->withHeader('Authorization', 'Bearer '.$this->jwt) @@ -125,6 +133,7 @@ class GameRankingApiTest extends TestCase ]); openssl_pkey_export($key, $privateKey); + $this->privateKey = $privateKey; $publicKey = openssl_pkey_get_details($key)['key']; config([ @@ -136,14 +145,14 @@ class GameRankingApiTest extends TestCase $this->jwt = $this->makeJwt($privateKey); } - private function makeJwt(string $privateKey): string + private function makeJwt(string $privateKey, string|array $audience = 'ranking-api'): string { $encode = fn (string $value): string => rtrim(strtr(base64_encode($value), '+/', '-_'), '='); $header = $encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])); $payload = $encode(json_encode([ 'iss' => 'gameverse-auth', - 'aud' => 'ranking-api', + 'aud' => $audience, 'sub' => 'consumer-project', 'iat' => time(), 'exp' => time() + 3600,