Auth no portal público
session(), login, PermissionGate, mad.auth.
Não existe mais uma distinção "admin vs. portal de cliente" com dois sistemas de login
separados. Para a navegação HTML há um único mecanismo de autenticação por sessão,
nativo do Laravel, que protege qualquer rota colocada atrás do middleware
mad.auth — seja uma tela de /app/*, um endpoint de API interna ou
uma futura área logada no portal público. O que varia é só onde você pendura o
middleware em routes/web.php/routes/modules/*.php. Fora do HTML
existe ainda a camada de API pública (mad.api), que é stateless e
autentica por token Bearer — descrita mais abaixo.
Visão geral
| Peça | Faz o quê |
|---|---|
session('logged'), session('userid'), etc. |
Identidade do usuário — chaves de sessão nativas do Laravel, sem facade Auth. |
App\Service\Iam\AuthenticationService |
Valida credenciais e popula as chaves de sessão no login. |
Mad\Security\PermissionGate |
Fonte única de verdade: "está logado?", "essa classe é pública?", "pode acessar esse programa/ação?". |
mad.auth (MadAuthenticate) |
Middleware: exige sessão logada, com allowlist de classes públicas. |
mad.permission (MadProgramPermission) |
Middleware: exige permissão de PROGRAMA/AÇÃO além de estar logado. |
Como o login popula a sessão
O formulário de login é uma tela comum do admin —
App\Control\Iam\LoginForm, registrada como qualquer outra:
// routes/web.php — PÚBLICAS (sem login), allowlist explícito
MadRoutes::expose('login', 'LoginForm'); // /app/login/{method?}
No submit, AuthenticationService::authenticate() valida usuário/senha e
loadSessionVars() grava as chaves de sessão usadas pelo resto do app
(todas via session() nativo — nada de Auth::login() nem
classe Mad\Web\AuthHelper):
// app/Service/Iam/AuthenticationService.php
public static function loadSessionVars($user, $reloadunit = true)
{
$programs = $user->getPrograms();
$programs['LoginForm'] = TRUE;
session(['logged' => TRUE]);
session(['login' => $user->login]);
session(['userid' => $user->id]);
session(['username' => $user->name]);
session(['usermail' => $user->email]);
session(['user_language' => $user->language ?: null]);
session(['programs' => $programs]);
session(['programs_actions' => $user->getProgramsActions()]);
// ... unit/tenant_id quando aplicável
}
PermissionGate — a fonte única de verdade
// packages/mad-framework/src/mad/security/PermissionGate.php
class PermissionGate
{
public static function isLogged(): bool
{
return (bool) session('logged');
}
/** Classes que QUALQUER visitante (sem login) pode acessar — config/mad.php */
public static function publicClasses(): array { /* ['permission']['public_classes'] */ }
public static function isPublic(string $class): bool
{
return in_array($class, self::publicClasses(), true);
}
public static function canAccess(string $class, ?string $method = null): bool { /* ... */ }
}
// config/mad.php
'permission' => [
'public_classes' => [
'SystemModulesCheckView',
'RegistrationForm',
'PasswordResetForm',
'RequestPasswordResetForm',
],
],
PermissionGate é a fonte única de verdade de autorização em todo o app —
usada pelos middlewares de rota (mad.auth via MadAuthenticate,
mad.permission via MadProgramPermission), pelo endpoint reativo do
MadWire (canAccessWire) e na diretiva/helper de visibilidade de menu — é o mesmo
gate em todo lugar, garantindo que "botão escondido" e "rota negada" nunca divirjam.
O middleware mad.auth passo a passo
// packages/mad-framework/src/mad/http/Middleware/MadAuthenticate.php
class MadAuthenticate
{
public function handle(Request $request, Closure $next)
{
if (PermissionGate::isLogged()) {
return $next($request);
}
$class = $this->resolveClass($request); // route('class') ou mad_state decifrado (wire)
if ($class === 'LoginForm'
|| in_array($class, PermissionGate::publicClasses(), true)
|| $class === $this->publicEntry() // config: general.public_view + public_entry
) {
return $next($request); // anônimo liberado
}
return $this->deny($request); // 401 JSON (wire/ajax) OU redirect script (navegação)
}
}
Na negação, o redirect usa MadRoutes::loginUrl() — a URL amigável calculada a
partir do slug routes.login.slug do locale ativo, nunca um caminho hardcoded.
O middleware mad.permission
Roda depois de mad.auth nas rotas de conteúdo. Não pergunta
"está logado?" (isso já foi resolvido) — pergunta "esse usuário pode acessar
$class/$method?":
// packages/mad-framework/src/mad/http/Middleware/MadProgramPermission.php
class MadProgramPermission
{
public function handle(Request $request, Closure $next)
{
$class = (string) ($request->route('class') ?? '');
$method = $request->route('method');
if ($class === '' || PermissionGate::canAccess($class, $method)) {
return $next($request);
}
return new Response('<h1>403 — Permission denied</h1>', 403);
}
}
// routes/modules/documents.php — padrão universal dos módulos de conteúdo
Route::middleware(['mad.auth', 'mad.permission'])->group(function () {
MadRoutes::resource('documents', 'DocumentList', 'DocumentForm');
});
Realmente público (sem conta de usuário nenhuma)
Para conteúdo que não precisa de identidade alguma — nem login, nem "usuário anônimo" —
a resposta é simplesmente não usar mad.auth. Os dois
exemplos reais deste app:
| Rota | Identidade | Mecanismo |
|---|---|---|
/docs/{section?}/{page?} |
Nenhuma | MadFrameworkDocs::showPage, zero middleware de auth — qualquer um lê. |
/public/ged/{token} |
Posse do token (64 hex) + senha opcional do link | GedPublicLinkController — sem login, mas com um "desbloqueio" por sessão quando o link tem senha. |
O desbloqueio por senha do GED é o caso mais próximo de um "portal de visitante" real neste app, e vale como referência de padrão: senha do link com hash (nunca texto plano) e o "desbloqueado" guardado na sessão do visitante, não num cookie/token separado:
// app/Service/Ged/GedSharedLinkService.php
$link->password_hash = $password ? password_hash($password, PASSWORD_BCRYPT) : null;
// app/Http/Controllers/GedPublicLinkController.php
public function unlock(Request $request, string $token): RedirectResponse|Response
{
$link = GedSharedLinkService::validateToken($token, $request->input('password', ''));
if (!$link) {
return $this->passwordPage($token, true); // senha errada
}
$request->session()->push(self::SESSION_UNLOCKED, $token); // guarda na sessão (Laravel nativo)
return redirect()->route('ged.public', ['token' => $token]);
}
Identidade sem sessão — a camada de API (mad.api)
Há um terceiro caminho de identidade, mais novo, que não usa cookie de sessão nenhum: as
rotas de routes/api.php, atrás do middleware mad.api. Ali a
identidade vem de um token (Authorization: Bearer mad_api_…),
e o token já carrega o escopo — cada um é amarrado a uma unidade, que resolve a empresa
(tenant) e o banco:
// routes/api.php — grupo `api` (sem StartSession, sem CSRF)
Route::middleware('mad.api')->apiResource('orders', OrderApiController::class);
Route::middleware('mad.api:orders.read')->get('/orders', ...); // ability por rota
| Aspecto | mad.auth (web) | mad.api (API) |
|---|---|---|
| Identidade | session('logged')/session('userid') | Token Bearer → user_id/unit_id do token |
| Estado | Sessão persistida (cookie) | Stateless: sessão só in-memory por request, flushada antes e depois |
| Autorização fina | mad.permission (programa/ação do IAM) | Abilities declaradas na rota (mad.api:orders.read) |
| Auditoria | HasMadAudit pela sessão do login | HasMadAudit pela session in-memory populada pelo middleware |
| CSRF | Sim (grupo web) | Não se aplica (sem cookie de sessão) |
O vínculo é revalidado a cada request (usuário existe e ativo, unidade existe/ativa e pertence ao usuário, empresa ativa) — um acesso revogado depois da emissão do token cai em 403 no request seguinte, sem depender de expiração. Contrato completo em REST API — Autenticação.
if (!session('logged')) { ... } espalhado pelos controllers — use o
middleware mad.auth na declaração da rota. Centralizar em
PermissionGate/MadAuthenticate é o que garante que o menu, o
canal reativo (canAccessWire) e as rotas HTTP nunca fiquem dessincronizados.
Próximos passos
- Middleware stack — onde
mad.auth/mad.permissionentram no pipeline. - Rotas públicas — as três camadas de acesso (pública/autenticada/autorizada).
- CSRF protection — como a mesma sessão também carrega o token CSRF.
- REST API — Autenticação — emissão de token, abilities e escopo de tenant do
mad.api.