gsebastiao / laravel-logintracker
Auditoria de login/logout, estado online em tempo real e ecra de bloqueio por inatividade para Laravel.
Package info
github.com/gsebastiao/laravel-logintracker
pkg:composer/gsebastiao/laravel-logintracker
Requires
- php: ^8.1
- illuminate/auth: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Sabe quem entrou, quem saiu, quem está online agora — e bloqueia o ecrã quando alguém se afasta do computador.
Pronto. O histórico de logins já está a ser gravado, sem escrever uma linha de código.
Índice
- Começar
- Quem está online
- Ecrã de bloqueio
- Logout automático ao expirar
- Personalizar o ecrã de bloqueio
- Bloquear manualmente
- Configuração
- Manutenção
- Migrations
- Problemas comuns
- Compatibilidade
- Tabelas
Começar
1. Instalar
composer require gsebastiao/laravel-logintracker
2. Criar as tabelas
php artisan migrate
Não precisa de copiar nada para o seu projeto: o pacote traz as próprias migrations e o migrate corre-as. A sua pasta database/migrations fica limpa. (Só quer editar as migrations antes de correr? Veja Migrations.)
Isto cria duas tabelas:
| Tabela | Para quê |
|---|---|
auth_logins |
Histórico: quem entrou, quando saiu, tentativas falhadas |
auth_sessions |
Estado atual: quem está online agora, quem está bloqueado |
3. Confirmar que funciona
Faça login na sua aplicação e depois:
php artisan tinker
\Gsebastiao\LoginTracker\Models\AuthLogin::latest()->first();
Se aparecer o seu login, está tudo a funcionar.
4. Publicar a configuração (opcional)
Só precisa disto se preferir editar o ficheiro de configuração:
php artisan vendor:publish --tag=logintracker-config
Quer mudar nomes de tabelas, tempos ou ativar o ecrã de bloqueio? Não precisa de publicar nada: basta acrescentar variáveis
LOGINTRACKER_*ao seu.env. A lista completa, com o padrão de cada uma, está em Configuração. Se for mudar o nome das tabelas, faça-o antes domigrate.
Quem está online
use Gsebastiao\LoginTracker\Facades\LoginTracker; LoginTracker::isOnline($user); // true / false LoginTracker::onlineDurationForHumans($user); // "2 horas e 15 minutos" LoginTracker::historyFor($user); // histórico completo
Todos os utilizadores online agora:
use Gsebastiao\LoginTracker\Models\AuthSession; AuthSession::online()->with('authenticatable')->get();
Como isto funciona
O browser envia um "sinal de vida" ao servidor de minuto a minuto. Enquanto esses sinais chegarem, o utilizador conta como online.
Isto é necessário porque uma sessão pode morrer em silêncio — o utilizador fecha o portátil, o browser fecha, a sessão expira — e nesses casos o Laravel não dispara nenhum evento de logout. Se contássemos apenas com os eventos, teríamos pessoas marcadas como "online" durante dias depois de terem saído.
Para os sinais serem enviados, adicione uma linha ao seu layout, antes de </body>:
@logintracker
Esta linha inclui tudo o que o pacote precisa no browser. Não é preciso colar mais nada.
Depois publique os ficheiros JavaScript:
php artisan vendor:publish --tag=logintracker-assets
Ecrã de bloqueio
Bloqueia o ecrã depois de um período sem o utilizador mexer no rato ou no teclado. Para continuar, é preciso introduzir a password.
No .env:
LOGINTRACKER_LOCKSCREEN=true LOGINTRACKER_IDLE_SECONDS=900
E no layout (a mesma linha de antes — se já a tem, não precisa de fazer nada):
@logintracker
Depois:
php artisan vendor:publish --tag=logintracker-assets
php artisan config:clear
php artisan view:clear
O bloqueio é real, não apenas visual
O estado do bloqueio fica guardado no servidor. Isto significa que:
- Recarregar a página (F5) não desbloqueia. O ecrã volta a aparecer imediatamente.
- Abrir uma aba nova não contorna o bloqueio. Todas as abas da mesma sessão ficam bloqueadas.
- Fechar e reabrir o browser não desbloqueia, enquanto a sessão for válida.
Só a password correta — ou terminar sessão — desbloqueia.
Logout automático ao expirar
Quando a sessão do utilizador expira enquanto ele tem a página aberta, o pacote termina a sessão e leva-o para o ecrã de login automaticamente. Vem ativo por defeito.
O utilizador não precisa de clicar em nada: até um minuto depois de a sessão morrer, o ecrã dele fica no login.
Isto é diferente do ecrã de bloqueio:
| Ecrã de bloqueio | Logout automático | |
|---|---|---|
| A sessão | continua válida | expirou |
| Para voltar | password | login completo |
| Causa | inatividade (rato/teclado) | tempo de sessão esgotado |
Para desligar e mostrar apenas um aviso:
LOGINTRACKER_AUTO_LOGOUT=false
Personalizar o ecrã de bloqueio
Opção 1 — editar o ecrã que vem incluído
php artisan vendor:publish --tag=logintracker-views
O ficheiro aparece em resources/views/vendor/logintracker/lockscreen.blade.php. O Laravel passa a usar a sua cópia automaticamente — edite à vontade.
Opção 2 — usar uma view totalmente sua
Em config/logintracker.php:
'lockscreen' => [ 'view' => 'partials.meu-lockscreen', ],
Variáveis disponíveis na view
Chegam sozinhas, seja qual for a view que usar:
| Variável | O que é |
|---|---|
$ltUser |
O utilizador autenticado (o modelo completo) |
$ltUserName |
Nome pronto a mostrar |
$ltUserAvatarUrl |
Avatar (gera um automaticamente se não existir) |
$ltIsLocked |
Se a sessão está bloqueada neste momento |
$ltUnlockUrl |
Para onde enviar a password |
$ltLogoutUrl |
Botão de terminar sessão (null se desativado) |
$ltCsrfToken |
Token CSRF |
Se criar uma view de raiz
O JavaScript precisa de encontrar os elementos. Mantenha estes IDs:
#logintracker-lockscreen <!-- o overlay --> #logintracker-unlock-form <!-- o formulário --> #logintracker-password <!-- o campo de password --> #logintracker-error <!-- onde os erros aparecem -->
E aplique display: {{ $ltIsLocked ? 'flex' : 'none' }} ao overlay — é isto que faz o bloqueio sobreviver a um refresh.
Bloquear manualmente
Um botão na página
<button onclick="window.LoginTrackerLockscreen.lock()">Bloquear agora</button>
Bloquear a sessão de outra pessoa
use Gsebastiao\LoginTracker\Facades\LoginTracker; LoginTracker::forceLock($user); // devolve o nº de sessões bloqueadas
Ou a partir do terminal:
php artisan logintracker:lock 5
php artisan logintracker:lock 5 --model="App\Models\Admin"
Bloqueia todas as sessões ativas daquela pessoa, em todos os dispositivos. O bloqueio aparece no ecrã dela até um minuto depois.
Configuração
Não precisa de publicar o ficheiro de configuração. O pacote carrega a sua
configuração sozinho e tudo se muda pelo .env. Só publique
(--tag=logintracker-config) se preferir editar o ficheiro.
| Variável | Padrão | Para quê |
|---|---|---|
LOGINTRACKER_CONNECTION |
(não definir) | Ligação de base de dados; sem ela, a da aplicação |
LOGINTRACKER_TABLE |
auth_logins |
Tabela do histórico (mude antes do migrate) |
LOGINTRACKER_SESSIONS_TABLE |
auth_sessions |
Tabela do estado atual (online/bloqueado) |
LOGINTRACKER_GUARD |
web,api |
Guards vigiados, separados por vírgulas; * = todos |
LOGINTRACKER_EVENT_LOGIN |
true |
Registar logins |
LOGINTRACKER_EVENT_LOGOUT |
true |
Registar logouts |
LOGINTRACKER_EVENT_FAILED |
true |
Registar tentativas falhadas |
LOGINTRACKER_CAPTURE_IPADDRESS |
true |
Guardar o IP |
LOGINTRACKER_CAPTURE_USERAGENT |
true |
Guardar o navegador (user agent) |
LOGINTRACKER_MORPH_NAME |
authenticatable |
Nome da coluna polimórfica do utilizador |
LOGINTRACKER_RETENTION_DAYS |
(não definir) | Apagar histórico com mais de N dias; sem valor, nunca apaga |
LOGINTRACKER_HEARTBEAT_ENABLE |
true |
Liga o sinal de vida (estado online) |
LOGINTRACKER_HEARTBEAT_MIDDLEWARE_ENABLE |
true |
Cada pedido normal também conta como sinal de vida |
LOGINTRACKER_HEARTBEAT_MIDDLEWARE_THROTTLE |
30 |
Segundos mínimos entre duas gravações do middleware |
LOGINTRACKER_HEARTBEAT_ROUTE_PATH |
logintracker/heartbeat |
Rota do sinal de vida |
LOGINTRACKER_HEARTBEAT_PING_INTERVAL |
60 |
De quanto em quanto tempo (s) o browser envia sinal |
LOGINTRACKER_HEARTBEAT_ONLINE_THRESHOLD |
120 |
Sem sinal há mais de N segundos = offline |
LOGINTRACKER_HEARTBEAT_STALE_MINUTES |
30 |
Sem sinal há mais de N minutos = sessão morta |
LOGINTRACKER_HEARTBEAT_AUTO_LOGOUT |
true |
Terminar a sessão no ecrã quando ela expirar |
LOGINTRACKER_HEARTBEAT_EXPIRED_REDIRECT |
(não definir) | Rota para onde ir ao expirar; sem valor, a de login |
LOGINTRACKER_LOCKSCREEN_ENABLE |
false |
Liga o ecrã de bloqueio |
LOGINTRACKER_LOCKSCREEN_IDLE_SECONDS |
900 |
Segundos de inatividade até bloquear (0 = só manual) |
LOGINTRACKER_LOCKSCREEN_VIEW |
logintracker::lockscreen |
View do ecrã de bloqueio |
LOGINTRACKER_LOCKSCREEN_UNLOCK_ROUTE_PATH |
logintracker/unlock |
Rota de desbloqueio |
LOGINTRACKER_LOCKSCREEN_ALLOW_LOGOUT |
true |
Botão "Terminar sessão" no ecrã de bloqueio |
LOGINTRACKER_LOCKSCREEN_LOGIN_ROUTE |
login |
Nome da rota de login da sua aplicação |
LOGINTRACKER_LOCKSCREEN_MAX_UNLOCK |
5 |
Tentativas de password antes de esperar |
LOGINTRACKER_LOCKSCREEN_UNLOCK_THROTTLE |
60 |
Segundos de espera após exceder as tentativas |
Exemplo de .env — ponha só as linhas que quiser mudar:
LOGINTRACKER_GUARD=web,admin LOGINTRACKER_RETENTION_DAYS=90 LOGINTRACKER_LOCKSCREEN_ENABLE=true LOGINTRACKER_LOCKSCREEN_IDLE_SECONDS=600
Não deixe uma variável em branco (
LOGINTRACKER_CONNECTION=): o Laravel lê isso como texto vazio, não como "sem valor". Para voltar ao padrão, apague a linha. Comphp artisan config:cache, rode-o de novo depois de mudar o.env.
Ao mexer nos tempos, respeite esta ordem:
HEARTBEAT_PING_INTERVAL < HEARTBEAT_ONLINE_THRESHOLD < HEARTBEAT_STALE_MINUTES × 60
60s 120s 1800s
Se inverter, as pessoas aparecem offline segundos depois de entrarem.
Manutenção
Uma tarefa agendada trata de fechar sessões mortas e limpar histórico antigo:
// routes/console.php (Laravel 11+) use Illuminate\Support\Facades\Schedule; Schedule::command('logintracker:purge')->everyFiveMinutes();
// app/Console/Kernel.php (Laravel 10 e anteriores) $schedule->command('logintracker:purge')->everyFiveMinutes();
Sem isto, sessões de pessoas que desligaram o computador ficam por fechar.
Também pode correr à mão:
php artisan logintracker:purge
php artisan logintracker:purge --sessions # só fechar sessões mortas
php artisan logintracker:purge --history # só limpar histórico antigo
php artisan logintracker:purge --days=90
Migrations
Publicar as migrations é opcional. Por defeito, php artisan migrate corre as migrations que vêm dentro do pacote, e a sua pasta database/migrations não recebe nada. O pacote usa dois ficheiros, e só dois, hoje e no futuro:
| Ficheiro | Para quê |
|---|---|
2026_01_01_000000_create_login_tracker_table.php |
Cria as tabelas. Corre uma vez. |
2026_01_01_000001_add_login_tracker_table.php |
Todas as alterações futuras. |
Quando sair uma versão nova do pacote com colunas novas, basta atualizar o pacote e correr php artisan migrate. Cada alteração verifica primeiro se já existe na base de dados, por isso pode correr as vezes que quiser sem dar erro.
Quero editar as migrations
Só nesse caso copie-as para o seu projeto, antes do primeiro migrate:
php artisan vendor:publish --tag=logintracker-migrations
Os ficheiros mantêm o nome original, e o Laravel usa a sua cópia em vez da do pacote (nunca corre as duas). Para acrescentar colunas suas, acrescente-as no fim do método up() do ficheiro add_..., seguindo o padrão de verificação que já lá está.
Com cópia publicada, as versões novas do pacote não a atualizam sozinhas. Depois de atualizar o pacote, volte a publicar:
php artisan vendor:publish --tag=logintracker-migrations --force
php artisan migrate
(o --force substitui as suas edições nesses ficheiros.)
Já tinha publicado as migrations antes? Continua tudo a funcionar, desde que os ficheiros mantenham o nome original — o Laravel identifica as migrations pelo nome, por isso a sua cópia substitui a do pacote e nenhuma corre duas vezes. Se lhes tiver mudado o timestamp, apague-as e volte a publicar com
--force.
Problemas comuns
O ecrã de bloqueio não aparece
Por esta ordem:
LOGINTRACKER_LOCKSCREEN_ENABLE=trueestá no.env?- A linha
@logintrackerestá no layout que esta página usa? - Correu
php artisan vendor:publish --tag=logintracker-assets? - Correu
php artisan config:clearephp artisan view:clear? - Está autenticado? Em páginas públicas não aparece nada.
A consola diz #logintracker-lockscreen nao existe nesta pagina
Falta @logintracker no layout. Ter a variável no .env não chega — é a diretiva que coloca o ecrã na página.
Mudei o .env e nada mudou
php artisan config:clear
php artisan view:clear
A sessão expira mas não vou para o login
Confirme que @logintracker está no layout e que os ficheiros JavaScript foram publicados. Abra a consola do browser (F12) e veja se há erro 404 em vendor/logintracker/heartbeat.js.
Já uso o pacote e quero atualizar da v1
A configuração mudou de nome, de login-tracker.php para logintracker.php:
php artisan vendor:publish --tag=logintracker-config
php artisan migrate
php artisan config:clear && php artisan view:clear
(Se tinha publicado as migrations, volte a publicá-las com --tag=logintracker-migrations --force antes do migrate.)
Depois substitua @loginTrackerLockscreen por @logintracker no layout (o nome antigo continua a funcionar) e remova quaisquer blocos <script> do LoginTracker que tenha colado à mão — a diretiva trata disso sozinha agora.
Compatibilidade
Funciona com Breeze, Jetstream, Fortify, Sanctum e Passport, porque escuta os eventos de autenticação do próprio Laravel em vez de depender de qualquer um deles.
Uma ressalva com o Fortify: logins e logouts são sempre registados, mas as tentativas falhadas podem não ser, consoante a ordem das ações em Fortify::authenticateThrough() no seu FortifyServiceProvider. É um comportamento do Fortify, não do pacote. Se depende disso para deteção de ataques, teste com uma password errada e confirme que aparece um registo com event = 'failed'.
Tabelas
auth_logins — histórico
| Coluna | |
|---|---|
authenticatable_id / _type |
Quem (funciona com qualquer modelo) |
guard |
web, api, … |
event |
login, logout, failed |
ip_address / user_agent |
De onde |
identifier |
Email usado numa tentativa falhada |
login_at / logout_at |
Quando entrou e saiu |
logout_reason |
Porque saiu (ver abaixo) |
meta |
Campo livre (JSON) |
logout_reason distingue três situações diferentes:
| Valor | Significa |
|---|---|
manual |
Clicou em "Sair" |
expired_client |
A sessão expirou e o browser dele reagiu sozinho |
inferred_stale |
Ninguém confirmou nada — deduzido mais tarde pela limpeza |
A diferença importa em auditoria: os dois primeiros são factos, o terceiro é uma estimativa.
auth_sessions — estado atual
| Coluna | |
|---|---|
authenticatable_id / _type |
Quem |
guard |
Qual guard |
session_id |
Distingue dispositivos da mesma pessoa |
started_at |
Início da sessão |
last_seen_at |
Último sinal de vida — decide se está online |
locked_at |
Ecrã bloqueado desde quando |
lock_requested_at |
Bloqueio remoto por entregar |
ended_at |
Fim da sessão |
Licença
MIT