funcional a parte de token
This commit is contained in:
@@ -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
|
||||
|
||||
13
README.md
13
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user