🔐 Autenticação de API
APIs precisam de saber quem estás e o que podes fazer. A autenticação garante que apenas utilizadores autorizados acedam aos recursos.
Tipos de Autenticação
🔑 API Key
Chave simples enviada em cada pedido. Comum em APIs públicas.
Segurança: Baixa-Média
👤 Basic Auth
Username e password codificados em Base64. Simples mas inseguro.
Segurança: Baixa (só com HTTPS)
🎫 Bearer Token
Token de acesso enviado no header. Permite scopes e permissões.
Segurança: Média-Alta
🔄 OAuth 2.0
Protocolo completo para autorização. Usado por Google, Facebook, etc.
Segurança: Alta
🔑 API Key
A forma mais simples de autenticação. Uma chave única que identifica a tua aplicação.
Como Funciona
A chave é enviada em cada pedido
Header
<?php
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://api.exemplo.com',
'headers' => [
'X-API-Key' => getenv('API_KEY'), // Melhor que colocar diretamente!
'Accept' => 'application/json'
]
]);
$response = $client->get('/users');
?>
Query Parameter
<?php
// Menos seguro - visível na URL
$response = $client->get('/users', [
'query' => [
'api_key' => getenv('API_KEY')
]
]);
?>
- Nunca coloques API keys no código fonte (usa
.env) - Não commits API keys para repositórios públicos
- Usa HTTPS para proteger a chave em trânsito
- Regista as chaves para detetar uso indevido
👤 Basic Authentication
Como Funciona
Envia username e password codificados em Base64 no header Authorization.
<?php
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'https://api.exemplo.com']);
// Método 1: Usando opção 'auth'
$response = $client->get('/protected', [
'auth' => [
'username',
'password'
]
]);
// Método 2: Header manual
$credentials = base64_encode('username:password');
$response = $client->get('/protected', [
'headers' => [
'Authorization' => 'Basic ' . $credentials
]
]);
?>
cURL
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.exemplo.com/protected',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => 'username:password'
]);
$response = curl_exec($ch);
curl_close($ch);
?>
Basic Auth envia as credenciais em Base64 (que é facilmente reversível). Sempre usa HTTPS para proteger as credenciais!
🎫 Bearer Token
O que é?
Um token de acesso (geralmente JWT ou OAuth token) que autoriza o acesso a recursos.
<?php
use GuzzleHttp\Client;
// Token obtido previamente (login, OAuth, etc.)
$token = getenv('ACCESS_TOKEN');
$client = new Client([
'base_uri' => 'https://api.exemplo.com',
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json'
]
]);
// Todos os pedidos usarão o token automaticamente
$response = $client->get('/protected-resource');
?>
Token por Pedido
<?php
$client = new Client(['base_uri' => 'https://api.exemplo.com']);
$response = $client->get('/resource', [
'headers' => [
'Authorization' => 'Bearer ' . $token
]
]);
?>
Renovar Token Expirado
<?php
class ApiClient {
private $client;
private $token;
private $tokenExpiry;
public function __construct() {
$this->client = new Client(['base_uri' => 'https://api.exemplo.com']);
}
private function ensureValidToken() {
if (!$this->token || time() >= $this->tokenExpiry) {
$this->refreshToken();
}
}
private function refreshToken() {
$response = $this->client->post('/auth/refresh', [
'json' => [
'refresh_token' => getenv('REFRESH_TOKEN')
]
]);
$data = json_decode($response->getBody(), true);
$this->token = $data['access_token'];
$this->tokenExpiry = time() + $data['expires_in'] - 60; // 1 min margem
}
public function request($method, $uri, $options = []) {
$this->ensureValidToken();
$options['headers']['Authorization'] = 'Bearer ' . $this->token;
return $this->client->request($method, $uri, $options);
}
}
?>
🔄 OAuth 2.0
O que é OAuth 2.0?
Protocolo de autorização que permite a aplicações aceder a recursos em nome de um utilizador, sem saber a password.
Fluxo Authorization Code
Obter Token OAuth
<?php
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'https://oauth.exemplo.com']);
// Trocar authorization code por token
$response = $client->post('/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'client_id' => getenv('OAUTH_CLIENT_ID'),
'client_secret' => getenv('OAUTH_CLIENT_SECRET'),
'redirect_uri' => 'https://meusite.com/callback'
]
]);
$tokenData = json_decode($response->getBody(), true);
// Guardar tokens
$accessToken = $tokenData['access_token'];
$refreshToken = $tokenData['refresh_token'];
$expiresIn = $tokenData['expires_in'];
// Usar o access token
$apiClient = new Client([
'base_uri' => 'https://api.exemplo.com',
'headers' => [
'Authorization' => 'Bearer ' . $accessToken
]
]);
?>
Client Credentials Flow
Para autenticação máquina-a-máquina (sem utilizador):
<?php
$response = $client->post('/oauth/token', [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => getenv('CLIENT_ID'),
'client_secret' => getenv('CLIENT_SECRET'),
'scope' => 'read write'
]
]);
$tokenData = json_decode($response->getBody(), true);
$accessToken = $tokenData['access_token'];
?>
Refresh Token
<?php
// Quando o access token expira, usar refresh token
$response = $client->post('/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => $storedRefreshToken,
'client_id' => getenv('CLIENT_ID'),
'client_secret' => getenv('CLIENT_SECRET')
]
]);
$newTokenData = json_decode($response->getBody(), true);
?>
📜 JWT (JSON Web Token)
O que é JWT?
Um token compacto e autossuficiente que contém informações (claims) codificadas em JSON.
Estrutura do JWT
Cada parte é codificada em Base64
Decodificar JWT
<?php
// Decodificar JWT sem biblioteca
function decodeJwt($token) {
$parts = explode('.', $token);
if (count($parts) !== 3) {
throw new Exception('Token inválido');
}
$header = json_decode(base64_decode($parts[0]), true);
$payload = json_decode(base64_decode($parts[1]), true);
return [
'header' => $header,
'payload' => $payload,
'signature' => $parts[2]
];
}
// Uso
$jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
$decoded = decodeJwt($jwt);
echo "ID: " . $decoded['payload']['sub'];
echo "Expira: " . date('Y-m-d H:i:s', $decoded['payload']['exp']);
?>
Verificar JWT com Biblioteca
# Instalar
composer require firebase/php-jwt
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
$key = 'sua_chave_secreta';
try {
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
print_r($decoded);
} catch (\Exception $e) {
echo "Token inválido: " . $e->getMessage();
}
?>
Criar JWT
<?php
use Firebase\JWT\JWT;
$payload = [
'iss' => 'https://meusite.com', // Emissor
'sub' => '123', // ID do utilizador
'iat' => time(), // Criado em
'exp' => time() + 3600, // Expira em 1 hora
'name' => 'João Silva',
'email' => 'joao@exemplo.com'
];
$jwt = JWT::encode($payload, $key, 'HS256');
echo $jwt;
?>
🛡️ Boas Práticas de Segurança
✅ Fazer
- ✓ Usar variáveis de ambiente
- ✓ Sempre usar HTTPS
- ✓ Guardar tokens de forma segura
- ✓ Implementar refresh tokens
- ✓ Validar tokens no servidor
- ✓ Usar expiração curta em tokens
❌ Evitar
- ✗ Hardcoded credentials no código
- ✗ Commits de .env para Git
- ✗ HTTP sem SSL
- ✗ Guardar tokens em localStorage
- ✗ Tokens sem expiração
- ✗ Ignorar validação de tokens
Variáveis de Ambiente
# .env
API_KEY=sk_live_abc123xyz
API_SECRET=secret_456
DATABASE_URL=mysql://user:pass@localhost/db
# Instalar vlucas/phpdotenv
composer require vlucas/phpdotenv
<?php
require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
// Usar as variáveis
$apiKey = $_ENV['API_KEY'];
$apiSecret = $_ENV['API_SECRET'];
// Ou com getenv()
$apiKey = getenv('API_KEY');
?>
.gitignore
# Nunca commitar estes ficheiros!
.env
.env.local
.env.production
*.pem
*.key
credentials.json
Usa diferentes API keys para desenvolvimento e produção. Nunca uses uma chave de produção em ambiente de desenvolvimento!
📝 Exercícios
Exercício 1: API Key
Desafio: Cria um cliente Guzzle que usa API Key no header para autenticação.
<?php
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://api.exemplo.com',
'headers' => [
'X-API-Key' => getenv('API_KEY'),
'Accept' => 'application/json'
]
]);
$response = $client->get('/users');
?>
Exercício 2: Bearer Token
Desafio: Faz um pedido autenticado com Bearer token obtido de uma variável de ambiente.
<?php
use GuzzleHttp\Client;
$token = getenv('ACCESS_TOKEN');
$client = new Client(['base_uri' => 'https://api.exemplo.com']);
$response = $client->get('/protected', [
'headers' => [
'Authorization' => 'Bearer ' . $token
]
]);
$data = json_decode($response->getBody(), true);
?>
Exercício 3: Tratamento de Token Expirado
Desafio: Implementa tratamento quando o token expira (código 401) e tenta renovar.
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
$client = new Client(['base_uri' => 'https://api.exemplo.com']);
$token = getenv('ACCESS_TOKEN');
try {
$response = $client->get('/resource', [
'headers' => ['Authorization' => 'Bearer ' . $token]
]);
} catch (ClientException $e) {
if ($e->getCode() === 401) {
// Token expirado - tentar renovar
$newToken = refreshToken();
// Tentar novamente com novo token
$response = $client->get('/resource', [
'headers' => ['Authorization' => 'Bearer ' . $newToken]
]);
} else {
throw $e;
}
}
function refreshToken() {
$client = new Client(['base_uri' => 'https://auth.exemplo.com']);
$response = $client->post('/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => getenv('REFRESH_TOKEN')
]
]);
$data = json_decode($response->getBody(), true);
return $data['access_token'];
}
?>