Segurança
Guard System
O Guard System é o sistema de autenticação oficial do OfficeGest, desenhado para ser leve, seguro e fácil de usar. Fornece gestão unificada para autenticação baseada em tokens (APIs) e gestão de sessão para SPAs.
O Que é o Guard?
Se já trabalhaste com autenticação em Laravel, provavelmente conheces o Sanctum ou Passport. O Guard é a nossa versão simplificada desses conceitos, adaptada às necessidades específicas do OfficeGest. Em vez de depender de pacotes externos pesados, criámos uma solução interna que faz exactamente o que precisamos — nem mais, nem menos.
O Guard resolve três problemas principais:
- Autenticação de APIs: Quando uma aplicação mobile ou um serviço externo precisa de aceder aos dados do ERP, usa tokens Bearer para se identificar.
- Autenticação de SPAs: Quando o frontend Vue/React está no mesmo domínio, podemos usar cookies seguros em vez de tokens (mais seguro contra XSS).
- Integração com o sistema legacy: O Guard foi desenhado para funcionar lado a lado com o sistema de autenticação existente do OfficeGest, sem conflitos.
Arquitectura
O sistema é composto por três componentes principais que trabalham em conjunto:
GuardManager
É o orquestrador central de toda a autenticação. Quando um pedido HTTP chega à aplicação, o GuardManager é responsável por extrair o token (do header, query string ou cookie), validá-lo contra a base de dados, e carregar o utilizador correspondente. Pensa nele como o "porteiro" que verifica credenciais antes de deixar alguém entrar.
Personal Access Tokens
São tokens de longa duração associados a um utilizador específico. Cada token tem um nome descritivo (por exemplo, "iPhone de Paulo" ou "Integração Contabilidade") e pode ter permissões limitadas. Os tokens são armazenados como hashes SHA-256 na base de dados — o token real é mostrado apenas uma vez, no momento da criação.
Middleware de Autenticação
É a camada que intercepta pedidos HTTP antes de chegarem aos controllers. Se uma rota está protegida pelo middleware guard.auth, o pedido só passa se o utilizador estiver autenticado. Caso contrário, recebe um erro 401. Também existe o middleware guard.abilities para verificar permissões específicas do token.
Quick Start
Vamos ver como proteger uma rota e autenticar um utilizador. Este é o fluxo mais comum que vais usar.
1. Proteger Rotas com Middleware
No ficheiro de rotas da API, agrupa as rotas que precisam de autenticação dentro do middleware guard.auth. Qualquer pedido sem um token válido será rejeitado automaticamente.
use Og\Modules\Auth\Api\Controllers\AuthController;
// Em routes/api.php
Route::middleware('guard.auth')->group(function () {
// Esta rota só é acessível com token válido
Route::get('/user', [AuthController::class, 'me']);
// Esta rota exige token válido E permissão específica
Route::get('/orders', [OrderController::class, 'index'])
->middleware('guard.abilities:orders:read');
});
2. Criar Endpoint de Login
O endpoint de login recebe as credenciais, verifica se são válidas, e devolve um token que o cliente deve guardar e usar nos pedidos seguintes. Nota que o token é gerado uma única vez — se o utilizador o perder, terá de criar um novo.
use Og\Modules\Common\Guard\GuardFacade as Guard;
public function store(AuthApiRequest $request)
{
// Validar input (o FormRequest já faz isto)
$data = $request->validated();
// Tentar autenticar com username e password
$user = Guard::attemptLogin($data);
if (empty($user)) {
return response()->json([
'error' => 'Credenciais inválidas'
], 401);
}
// Criar token com nome descritivo
// O nome ajuda o utilizador a identificar onde está a usar o token
$token = Guard::createToken($user->getId(), 'api-mobile-app');
return response()->json([
'data' => [
'access_token' => $token->getPlainTextToken(),
'token_type' => 'Bearer',
'user' => [
'id' => $user->getId(),
'name' => $user->getName(),
]
]
]);
}
Verificar Autenticação
Dentro de um controller ou action, muitas vezes precisamos de saber quem está autenticado. A Facade Guard fornece métodos simples para isso.
Verificar se Está Autenticado
O método check() retorna true se existe um utilizador autenticado, e guest() retorna o inverso. São úteis para lógica condicional.
use Og\Modules\Common\Guard\GuardFacade as Guard;
// Em qualquer controller ou action:
if (Guard::check()) {
// Utilizador está autenticado
$user = Guard::user(); // Objeto User completo
$userId = Guard::id(); // Apenas o ID (mais performante)
echo "Bem-vindo, " . $user->getName();
}
if (Guard::guest()) {
// Visitante não autenticado
return redirect('/login');
}
Token Abilities (Scopes)
Nem todos os tokens devem ter acesso total ao sistema. Por exemplo, uma integração com um parceiro pode precisar apenas de ler produtos, mas não de criar facturas. As "Abilities" (também conhecidas como Scopes) permitem limitar o que cada token pode fazer.
Isto é especialmente importante para tokens de longa duração — se um token for comprometido, o dano é limitado às permissões que lhe foram atribuídas.
Criar Token com Permissões Limitadas
Ao criar um token, passa um array com as permissões que ele deve ter. Por padrão, se não passares nada, o token terá acesso total (["*"]).
// Token com acesso limitado a produtos e stocks
$token = Guard::createToken(
$user->getId(),
'integracao-erp-parceiro',
['products:read', 'stocks:read', 'stocks:update']
);
// Token sem restrições (usa apenas para apps internas de confiança)
$fullAccessToken = Guard::createToken(
$user->getId(),
'app-mobile-interno'
// Sem terceiro argumento = acesso total
);
Verificar Abilities
Existem duas formas de verificar abilities: via código ou via middleware. O middleware é preferível para proteger rotas inteiras; a verificação em código é útil para lógica mais granular.
// Opção 1: Verificar no código
public function updateStock(Request $request, int $productId)
{
if (!Guard::tokenCan('stocks:update')) {
return response()->json([
'error' => 'Este token não tem permissão para actualizar stocks'
], 403);
}
// Proceder com a actualização...
}
// Opção 2: Proteger a rota com middleware (mais limpo)
Route::post('/stocks/{id}', [StockController::class, 'update'])
->middleware('guard.auth', 'guard.abilities:stocks:update');
Autenticação para SPAs
Se o teu frontend (Vue, React, ou vanilla JS) está no mesmo domínio que o backend, não precisas de usar tokens Bearer. Podes usar autenticação baseada em cookies, que é mais segura contra ataques XSS.
A diferença é simples: em vez de o cliente guardar o token e enviá-lo em cada pedido, o browser gere automaticamente o cookie. Isto significa que mesmo que código malicioso seja injectado na página, não consegue aceder ao cookie (se estiver marcado como HttpOnly).
Login para SPA
public function spaLogin(Request $request)
{
$credentials = $request->only('username', 'password');
// loginForSpa() cria uma sessão baseada em cookie
if ($token = Guard::loginForSpa($credentials)) {
return response()->json([
'message' => 'Login bem sucedido',
'user' => Guard::user()->toArray()
]);
}
return response()->json([
'error' => 'Credenciais inválidas'
], 401);
}
Logout e Revogação de Tokens
Existem dois cenários de logout que deves considerar:
Revogar Apenas o Token Actual
Este é o logout "normal". O utilizador sai do dispositivo actual, mas continua logado noutros dispositivos (se tiver tokens activos).
public function logout()
{
Guard::logout();
// ou: Guard::revokeCurrentToken();
return response()->json([
'message' => 'Sessão terminada com sucesso'
]);
}
Revogar Todos os Tokens
Este é o "logout de emergência". Útil quando o utilizador muda a password, reporta actividade suspeita, ou simplesmente quer sair de todos os dispositivos de uma vez.
public function logoutEverywhere()
{
// Invalida TODOS os tokens do utilizador
Guard::revokeAllTokens();
return response()->json([
'message' => 'Todas as sessões foram terminadas'
]);
}
Como Funciona Por Dentro
Quando um pedido HTTP chega a uma rota protegida, o Guard procura o token nesta ordem de prioridade:
- 1. Header Authorization:
Authorization: Bearer <token>— Este é o método padrão para APIs REST. - 2. Query Parameter:
?api_token=<token>— Útil para webhooks ou links directos onde não é possível definir headers. - 3. Cookie: Nome configurado nas definições — Usado para autenticação de SPAs.
Se encontrar um token válido em qualquer uma destas fontes, carrega o utilizador correspondente e permite o acesso. Caso contrário, rejeita o pedido com erro 401.
Estrutura da Tabela personal_access_tokens
Os tokens são guardados nesta tabela. Nota que o token real nunca é armazenado — apenas o seu hash SHA-256. Isto significa que mesmo que alguém aceda à base de dados, não consegue usar os tokens.
| Coluna | Descrição |
|---|---|
| tokenable_id | ID do utilizador (empreg.codempr) |
| name | Nome descritivo do token (ex: "iPhone de Paulo") |
| token | Hash SHA-256 do token real |
| abilities | JSON com permissões (ex: ["products:read", "stocks:update"]) |
| last_used_at | Actualizado a cada pedido — útil para detectar tokens abandonados |