Админка
please wait

Declaração do Problema

Precisa de implementar um sistema de autenticação seguro que suporte múltiplos clientes (web, mobile, API), proteja contra ataques comuns e proporcione uma boa experiência de utilizador.

Arquitetura de Autenticação

┌─────────────────────────────────────────────────────────────────────────┐
│ Client Applications │
├─────────────────┬─────────────────┬─────────────────┬──────────────────┤
│ Web (SPA) │ Mobile App │ 3rd Party API │ Internal API │
│ HTTP-Only │ Secure Token │ OAuth 2.0 │ API Key + │
│ Cookies │ Storage │ Client Creds │ mTLS │
└────────┬────────┴────────┬────────┴────────┬────────┴────────┬─────────┘
│ │ │ │
└─────────────────┴────────┬────────┴─────────────────┘
│
┌─────────▼─────────┐
│ API Gateway │
│ Rate Limiting │
│ Token Validation│
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ Auth Service │
│ JWT Issuer │
│ Session Mgmt │
└─────────┬─────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ User DB │ │ Redis │ │ OAuth │
│ (hashed) │ │ (sessions) │ │ Providers │
└─────────────┘ └─────────────┘ └─────────────┘

Estratégia 1: JWT + Cookies HTTP-Only (Aplicações Web)

Implementação no Backend (Laravel)

// config/sanctum.php
'expiration' => 60, // O token expira em 60 minutos
// app/Http/Controllers/AuthController.php
class AuthController extends Controller
{
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!Auth::attempt($credentials)) {
// Use uma mensagem genérica para evitar a enumeração de utilizadores
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$user = Auth::user();
// Aplicar rate limiting às tentativas falhadas
RateLimiter::hit('login:' . $request->ip(), 300);
// Criar token
$token = $user->createToken('auth_token')->plainTextToken;
// Definir cookie HTTP-only para clientes web
$cookie = cookie(
'auth_token',
$token,
60, // minutos
'/',
null,
true, // secure (apenas HTTPS)
true, // httpOnly
false,
'Strict' // sameSite
);
return response()->json([
'user' => $user,
'message' => 'Login successful'
])->withCookie($cookie);
}
public function logout(Request $request)
{
// Revogar o token atual
$request->user()->currentAccessToken()->delete();
// Limpar cookie
$cookie = cookie()->forget('auth_token');
return response()->json(['message' => 'Logged out'])
->withCookie($cookie);
}
public function refresh(Request $request)
{
$user = $request->user();
// Revogar o token antigo
$user->currentAccessToken()->delete();
// Emitir um novo token
$token = $user->createToken('auth_token')->plainTextToken;
$cookie = cookie('auth_token', $token, 60, '/', null, true, true, false, 'Strict');
return response()->json(['message' => 'Token refreshed'])
->withCookie($cookie);
}
}

Middleware de Validação de Token

// app/Http/Middleware/ValidateToken.php
class ValidateToken
{
public function handle($request, Closure $next)
{
// Verificar primeiro o cookie e depois o header Authorization
$token = $request->cookie('auth_token')
?? $request->bearerToken();
if (!$token) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$accessToken = PersonalAccessToken::findToken($token);
if (!$accessToken || $accessToken->expires_at < now()) {
return response()->json(['error' => 'Token expired'], 401);
}
// Definir o utilizador autenticado
Auth::setUser($accessToken->tokenable);
return $next($request);
}
}

Implementação no Frontend (Vue.js/React)

// api/auth.js
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
withCredentials: true, // Importante: enviar cookies com os pedidos
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
});
// Adicionar token CSRF para Laravel
api.interceptors.request.use(config => {
const token = document.querySelector('meta[name="csrf-token"]')?.content;
if (token) {
config.headers['X-CSRF-TOKEN'] = token;
}
return config;
});
// Tratar a expiração do token
api.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401) {
// Tentar fazer refresh do token
try {
await api.post('/auth/refresh');
return api.request(error.config);
} catch (refreshError) {
// Redirecionar para o login
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);
export const auth = {
login: (email, password) => api.post('/auth/login', { email, password }),
logout: () => api.post('/auth/logout'),
getUser: () => api.get('/auth/user'),
};

Estratégia 2: OAuth 2.0 / Login Social

Configuração do Laravel Socialite

// config/services.php
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URL'),
],
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => env('APPLE_CLIENT_SECRET'),
'redirect' => env('APPLE_REDIRECT_URL'),
],
// app/Http/Controllers/SocialAuthController.php
class SocialAuthController extends Controller
{
public function redirect($provider)
{
// Validar o provider
if (!in_array($provider, ['google', 'apple', 'facebook'])) {
abort(404);
}
return Socialite::driver($provider)
->stateless()
->redirect();
}
public function callback($provider)
{
try {
$socialUser = Socialite::driver($provider)->stateless()->user();
} catch (Exception $e) {
return redirect('/login')->with('error', 'Authentication failed');
}
// Encontrar ou criar utilizador
$user = User::updateOrCreate(
['email' => $socialUser->getEmail()],
[
'name' => $socialUser->getName(),
'provider' => $provider,
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
]
);
// Criar token
$token = $user->createToken('social_auth')->plainTextToken;
// Redirecionar para o frontend com o token
return redirect(env('FRONTEND_URL') . '/auth/callback?token=' . $token);
}
}

Estratégia 3: Autenticação por API Key (Servidor-para-Servidor)

Implementação de API Key

// app/Models/ApiKey.php
class ApiKey extends Model
{
protected $fillable = ['name', 'key', 'permissions', 'rate_limit', 'expires_at'];
protected $casts = ['permissions' => 'array'];
public static function generate($name, $permissions = [])
{
$key = Str::random(32);
return self::create([
'name' => $name,
'key' => hash('sha256', $key),
'permissions' => $permissions,
'rate_limit' => 1000,
]);
}
public static function findByKey($key)
{
return self::where('key', hash('sha256', $key))->first();
}
}
// app/Http/Middleware/ValidateApiKey.php
class ValidateApiKey
{
public function handle($request, Closure $next, ...$permissions)
{
$key = $request->header('X-API-Key');
if (!$key) {
return response()->json(['error' => 'API key required'], 401);
}
$apiKey = ApiKey::findByKey($key);
if (!$apiKey) {
return response()->json(['error' => 'Invalid API key'], 401);
}
if ($apiKey->expires_at && $apiKey->expires_at < now()) {
return response()->json(['error' => 'API key expired'], 401);
}
// Verificar permissões
foreach ($permissions as $permission) {
if (!in_array($permission, $apiKey->permissions ?? [])) {
return response()->json(['error' => 'Insufficient permissions'], 403);
}
}
// Rate limiting
$rateLimitKey = 'api_key:' . $apiKey->id;
if (RateLimiter::tooManyAttempts($rateLimitKey, $apiKey->rate_limit)) {
return response()->json(['error' => 'Rate limit exceeded'], 429);
}
RateLimiter::hit($rateLimitKey, 3600);
$request->merge(['api_key' => $apiKey]);
return $next($request);
}
}

Segurança de Passwords

Hashing Seguro de Passwords

// Use sempre bcrypt ou argon2
$hashedPassword = Hash::make($password);
// Verificar
if (Hash::check($plainPassword, $hashedPassword)) {
// A password corresponde
}
// Verificar se é necessário rehash (atualização do algoritmo)
if (Hash::needsRehash($hashedPassword)) {
$user->password = Hash::make($plainPassword);
$user->save();
}

Regras de Validação de Password

// app/Rules/SecurePassword.php
class SecurePassword implements Rule
{
public function passes($attribute, $value)
{
return strlen($value) >= 12
&& preg_match('/[A-Z]/', $value)
&& preg_match('/[a-z]/', $value)
&& preg_match('/[0-9]/', $value)
&& preg_match('/[^A-Za-z0-9]/', $value)
&& !$this->isCommonPassword($value);
}
private function isCommonPassword($password)
{
$common = ['password123', '123456789', 'qwerty123'];
return in_array(strtolower($password), $common);
}
}

Autenticação Multi-Fator (MFA)

Implementação de TOTP

// A usar pragmarx/google2fa
use PragmaRX\Google2FA\Google2FA;
class MfaController extends Controller
{
public function enable(Request $request)
{
$google2fa = new Google2FA();
// Gerar segredo
$secret = $google2fa->generateSecretKey();
// Armazenar temporariamente na sessão
session(['mfa_secret' => $secret]);
// Gerar URL do código QR
$qrCodeUrl = $google2fa->getQRCodeUrl(
config('app.name'),
$request->user()->email,
$secret
);
return response()->json([
'secret' => $secret,
'qr_code_url' => $qrCodeUrl,
]);
}
public function verify(Request $request)
{
$google2fa = new Google2FA();
$secret = session('mfa_secret');
if (!$google2fa->verifyKey($secret, $request->code)) {
return response()->json(['error' => 'Invalid code'], 422);
}
// Guardar MFA no utilizador
$request->user()->update([
'mfa_secret' => encrypt($secret),
'mfa_enabled' => true,
]);
// Gerar códigos de backup
$backupCodes = collect(range(1, 10))->map(fn() => Str::random(8));
$request->user()->backupCodes()->createMany(
$backupCodes->map(fn($code) => ['code' => Hash::make($code)])
);
session()->forget('mfa_secret');
return response()->json([
'message' => 'MFA enabled',
'backup_codes' => $backupCodes,
]);
}
}

Segurança de Sessão

Configuração de Sessão com Redis

// config/session.php
'driver' => 'redis',
'lifetime' => 120,
'expire_on_close' => false,
'encrypt' => true,
'secure' => true,
'http_only' => true,
'same_site' => 'lax',

Invalidação de Sessão em Eventos de Segurança

// Invalidar todas as sessões ao alterar a password
class PasswordController extends Controller
{
public function update(Request $request)
{
$user = $request->user();
$user->password = Hash::make($request->new_password);
$user->save();
// Revogar todos os tokens
$user->tokens()->delete();
// Invalidar todas as sessões
DB::table('sessions')
->where('user_id', $user->id)
->delete();
// Criar nova sessão para o pedido atual
Auth::login($user);
}
}

Rate Limiting

// app/Http/Kernel.php
protected $middlewareGroups = [
'api' => [
'throttle:api',
],
];
// app/Providers/RouteServiceProvider.php
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(5)->by($request->ip()),
Limit::perMinute(10)->by($request->input('email')),
];
});

Cabeçalhos de Segurança

// app/Http/Middleware/SecurityHeaders.php
class SecurityHeaders
{
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
$response->headers->set('Strict-Transport-Security',
'max-age=31536000; includeSubDomains');
return $response;
}
}

Checklist de Segurança

Autenticação

  • [ ] Usar HTTPS em todo o lado
  • [ ] Armazenar passwords com bcrypt/argon2
  • [ ] Implementar bloqueio de conta após tentativas falhadas
  • [ ] Usar cookies HTTP-only, Secure, SameSite
  • [ ] Implementar proteção CSRF
  • [ ] Adicionar suporte para MFA
  • [ ] Gerar IDs de sessão seguros
  • [ ] Invalidar sessões ao terminar sessão e ao alterar a password

Segurança da API

  • [ ] Validar e sanitizar todos os inputs
  • [ ] Usar queries parametrizadas (prevenir SQL injection)
  • [ ] Implementar rate limiting
  • [ ] Usar API keys para servidor-para-servidor
  • [ ] Registar eventos de autenticação
  • [ ] Definir cabeçalhos CORS adequados

Segurança de Tokens

  • [ ] Expiração curta de tokens (15-60 minutos)
  • [ ] Implementar mecanismo de refresh de token
  • [ ] Armazenar refresh tokens de forma segura
  • [ ] Revogar tokens ao terminar sessão
  • [ ] Não armazenar tokens em localStorage (usar cookies HTTP-only)

A Mentalidade de Autenticação de um Senior

Engenheiros seniores tratam a autenticação e o controlo de acesso como sistemas críticos, não como extras. Concebem com predefinições seguras, visibilidade operacional e recuperação fácil.

Princípios-Chave

Threat Modeling Primeiro: Defina o que está a proteger e quem o poderá atacar. Identifique pontos de entrada como endpoints de login, fluxos de reposição de password e integrações com terceiros. Isto informa quão fortes precisam de ser os seus controlos de autenticação.

Segurança em Camadas: A segurança é multicamada — controlos físicos, de rede, de plataforma, de aplicação e de dados devem trabalhar em conjunto. Quando toda a stack está alinhada, a autenticação torna-se uma fronteira previsível em vez de uma margem frágil.

Monitorizar a Saúde da Autenticação: Os sistemas de autenticação devem expor métricas: taxa de sucesso de login, taxa de logins falhados, taxa de falhas de MFA e erros de refresh de token. Configure alertas para picos que possam indicar ataques ou indisponibilidades. A visibilidade é essencial porque problemas de autenticação muitas vezes parecem indisponibilidades gerais do sistema para os utilizadores.

Armadilhas Comuns a Evitar

  • Misturar lógica de autenticação e autorização num único passo
  • Tokens de longa duração sem rotação
  • Fluxos fracos de reposição de password
  • Falta de registos de auditoria para ações sensíveis
  • Permissões demasiado abrangentes que se tornam permanentes

Equilibrar Segurança com UX

Uma autenticação demasiado rígida pode afastar utilizadores, enquanto uma autenticação demasiado permissiva convida ao abuso. Forneça mensagens de erro claras, evite reautenticação desnecessária e guie os utilizadores através de fluxos seguros sem frustração. Uma boa UX é uma funcionalidade de segurança — os utilizadores têm maior probabilidade de seguir práticas seguras quando o sistema é claro e responsivo.

Essa confiança é difícil de reconstruir depois de perdida. Proteja-a.

Artigos Relacionados na Wiki

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive