Docs›Roteamento›Auth no portal público
Roteamento

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çaFaz 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:

RotaIdentidadeMecanismo
/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
Aspectomad.auth (web)mad.api (API)
Identidadesession('logged')/session('userid')Token Bearer → user_id/unit_id do token
EstadoSessão persistida (cookie)Stateless: sessão só in-memory por request, flushada antes e depois
Autorização finamad.permission (programa/ação do IAM)Abilities declaradas na rota (mad.api:orders.read)
AuditoriaHasMadAudit pela sessão do loginHasMadAudit pela session in-memory populada pelo middleware
CSRFSim (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.

Nunca implemente checagem de login na mão. Não escreva 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