Anvil
Anvil - The mobile companion for Laravel Forge. Available now. Download for iOS

Displaying the Log

You can set up your own views and paginate the logs using the user relationship as normal. Below are examples for different table implementations.

Filament Table

If you're using Filament, here's an example table component:

Note: This example uses the package's built-in DeviceFingerprint helper for parsing user agents. You can customize the display format as needed.

1<?php
2 
3namespace App\Filament\Resources;
4 
5use App\Filament\Resources\AuthenticationLogResource\Pages;
6use Filament\Resources\Resource;
7use Filament\Tables;
8use Filament\Tables\Table;
9use Illuminate\Database\Eloquent\Builder;
10use Rappasoft\LaravelAuthenticationLog\Helpers\DeviceFingerprint;
11use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog;
12 
13class AuthenticationLogResource extends Resource
14{
15 protected static ?string $model = AuthenticationLog::class;
16 
17 protected static ?string $navigationIcon = 'heroicon-o-shield-check';
18 
19 public static function table(Table $table): Table
20 {
21 return $table
22 ->columns([
23 Tables\Columns\TextColumn::make('ip_address')
24 ->label('IP Address')
25 ->searchable()
26 ->sortable(),
27 Tables\Columns\TextColumn::make('device_name')
28 ->label('Browser/Device')
29 ->searchable()
30 ->default('Unknown Device'),
31 Tables\Columns\TextColumn::make('user_agent')
32 ->label('User Agent')
33 ->searchable()
34 ->wrap()
35 ->toggleable(isToggledHiddenByDefault: true),
36 Tables\Columns\TextColumn::make('location')
37 ->label('Location')
38 ->searchable(query: function (Builder $query, string $search): Builder {
39 return $query
40 ->where('location->city', 'like', "%{$search}%")
41 ->orWhere('location->state', 'like', "%{$search}%")
42 ->orWhere('location->state_name', 'like', "%{$search}%")
43 ->orWhere('location->postal_code', 'like', "%{$search}%");
44 })
45 ->formatStateUsing(function ($state) {
46 if (!$state || ($state['default'] ?? false)) {
47 return '-';
48 }
49 return ($state['city'] ?? 'Unknown City') . ', ' . ($state['state'] ?? 'Unknown State');
50 }),
51 Tables\Columns\TextColumn::make('device_name')
52 ->label('Device')
53 ->default('Unknown')
54 ->searchable(),
55 Tables\Columns\IconColumn::make('login_successful')
56 ->label('Status')
57 ->boolean()
58 ->trueIcon('heroicon-o-check-circle')
59 ->falseIcon('heroicon-o-x-circle')
60 ->trueColor('success')
61 ->falseColor('danger')
62 ->sortable(),
63 Tables\Columns\IconColumn::make('is_trusted')
64 ->label('Trusted')
65 ->boolean()
66 ->sortable(),
67 Tables\Columns\IconColumn::make('is_suspicious')
68 ->label('Suspicious')
69 ->boolean()
70 ->trueIcon('heroicon-o-exclamation-triangle')
71 ->trueColor('warning')
72 ->sortable(),
73 Tables\Columns\TextColumn::make('login_at')
74 ->label('Login At')
75 ->dateTime()
76 ->sortable()
77 ->default('-'),
78 Tables\Columns\TextColumn::make('logout_at')
79 ->label('Logout At')
80 ->dateTime()
81 ->sortable()
82 ->default('-'),
83 Tables\Columns\TextColumn::make('last_activity_at')
84 ->label('Last Activity')
85 ->dateTime()
86 ->sortable()
87 ->default('-'),
88 ])
89 ->filters([
90 Tables\Filters\TernaryFilter::make('login_successful')
91 ->label('Login Status')
92 ->placeholder('All logins')
93 ->trueLabel('Successful only')
94 ->falseLabel('Failed only'),
95 Tables\Filters\TernaryFilter::make('is_trusted')
96 ->label('Trusted Device')
97 ->placeholder('All devices')
98 ->trueLabel('Trusted only')
99 ->falseLabel('Untrusted only'),
100 Tables\Filters\TernaryFilter::make('is_suspicious')
101 ->label('Suspicious Activity')
102 ->placeholder('All activities')
103 ->trueLabel('Suspicious only')
104 ->falseLabel('Normal only'),
105 Tables\Filters\Filter::make('active_sessions')
106 ->label('Active Sessions')
107 ->query(fn (Builder $query): Builder => $query
108 ->where('login_successful', true)
109 ->whereNull('logout_at')
110 ),
111 ])
112 ->actions([
113 Tables\Actions\Action::make('view')
114 ->label('View Details')
115 ->icon('heroicon-o-eye')
116 ->modalContent(function (AuthenticationLog $record) {
117 return view('filament.resources.authentication-log.view', [
118 'record' => $record,
119 ]);
120 })
121 ->modalHeading('Authentication Log Details'),
122 ])
123 ->defaultSort('login_at', 'desc')
124 ->poll('30s'); // Optional: auto-refresh every 30 seconds
125 }
126 
127 public static function getEloquentQuery(): Builder
128 {
129 return parent::getEloquentQuery()
130 ->where('authenticatable_type', auth()->user()->getMorphClass())
131 ->where('authenticatable_id', auth()->id());
132 }
133}

For a standalone Filament table (not a resource), you can use:

1<?php
2 
3namespace App\Filament\Pages;
4 
5use Filament\Pages\Page;
6use Filament\Tables;
7use Filament\Tables\Concerns\InteractsWithTable;
8use Filament\Tables\Contracts\HasTable;
9use Filament\Tables\Table;
10use Illuminate\Database\Eloquent\Builder;
11use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog;
12 
13class AuthenticationLogs extends Page implements HasTable
14{
15 use InteractsWithTable;
16 
17 protected static ?string $navigationIcon = 'heroicon-o-shield-check';
18 
19 protected static string $view = 'filament.pages.authentication-logs';
20 
21 public function table(Table $table): Table
22 {
23 return $table
24 ->query(
25 AuthenticationLog::query()
26 ->where('authenticatable_type', auth()->user()->getMorphClass())
27 ->where('authenticatable_id', auth()->id())
28 )
29 ->columns([
30 // ... same columns as above
31 ])
32 ->filters([
33 // ... same filters as above
34 ])
35 ->defaultSort('login_at', 'desc');
36 }
37}

Displaying Authentication Logs on User Resource Pages

To show authentication logs as a relationship tab on your User resource page, create a RelationManager:

1<?php
2 
3namespace App\Filament\Resources\UserResource\RelationManagers;
4 
5use Filament\Resources\RelationManagers\RelationManager;
6use Filament\Tables;
7use Filament\Tables\Table;
8use Illuminate\Database\Eloquent\Builder;
9use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog;
10 
11class AuthenticationLogsRelationManager extends RelationManager
12{
13 protected static string $relationship = 'authentications';
14 
15 protected static ?string $recordTitleAttribute = 'ip_address';
16 
17 public function table(Table $table): Table
18 {
19 return $table
20 ->columns([
21 Tables\Columns\TextColumn::make('ip_address')
22 ->label('IP Address')
23 ->searchable()
24 ->sortable(),
25 Tables\Columns\TextColumn::make('device_name')
26 ->label('Device')
27 ->searchable()
28 ->default('Unknown Device'),
29 Tables\Columns\TextColumn::make('user_agent')
30 ->label('User Agent')
31 ->searchable()
32 ->wrap()
33 ->toggleable(isToggledHiddenByDefault: true),
34 Tables\Columns\TextColumn::make('location')
35 ->label('Location')
36 ->getStateUsing(function ($record) {
37 $location = $record->location;
38 
39 if (!$location || !is_array($location)) {
40 return '-';
41 }
42 
43 // Don't show default/fallback locations
44 if ($location['default'] ?? false) {
45 return '-';
46 }
47 
48 $city = $location['city'] ?? null;
49 $state = $location['state'] ?? $location['state_name'] ?? null;
50 
51 if (!$city && !$state) {
52 return '-';
53 }
54 
55 return trim(($city ?? '') . ($city && $state ? ', ' : '') . ($state ?? '')) ?: '-';
56 })
57 ->searchable(false),
58 Tables\Columns\IconColumn::make('login_successful')
59 ->label('Status')
60 ->boolean()
61 ->trueIcon('heroicon-o-check-circle')
62 ->falseIcon('heroicon-o-x-circle')
63 ->trueColor('success')
64 ->falseColor('danger')
65 ->sortable(),
66 Tables\Columns\IconColumn::make('is_trusted')
67 ->label('Trusted')
68 ->boolean()
69 ->sortable(),
70 Tables\Columns\IconColumn::make('is_suspicious')
71 ->label('Suspicious')
72 ->boolean()
73 ->trueIcon('heroicon-o-exclamation-triangle')
74 ->trueColor('warning')
75 ->sortable(),
76 Tables\Columns\TextColumn::make('login_at')
77 ->label('Login At')
78 ->dateTime()
79 ->sortable()
80 ->placeholder('-'),
81 Tables\Columns\TextColumn::make('logout_at')
82 ->label('Logout At')
83 ->dateTime()
84 ->sortable()
85 ->placeholder('-'),
86 Tables\Columns\TextColumn::make('last_activity_at')
87 ->label('Last Activity')
88 ->dateTime()
89 ->sortable()
90 ->placeholder('-'),
91 ])
92 ->filters([
93 Tables\Filters\TernaryFilter::make('login_successful')
94 ->label('Login Status')
95 ->placeholder('All logins')
96 ->trueLabel('Successful only')
97 ->falseLabel('Failed only'),
98 Tables\Filters\TernaryFilter::make('is_trusted')
99 ->label('Trusted Device')
100 ->placeholder('All devices')
101 ->trueLabel('Trusted only')
102 ->falseLabel('Untrusted only'),
103 Tables\Filters\TernaryFilter::make('is_suspicious')
104 ->label('Suspicious Activity')
105 ->placeholder('All activities')
106 ->trueLabel('Suspicious only')
107 ->falseLabel('Normal only'),
108 Tables\Filters\Filter::make('active_sessions')
109 ->label('Active Sessions')
110 ->query(fn (Builder $query): Builder => $query
111 ->where('login_successful', true)
112 ->whereNull('logout_at')
113 ),
114 ])
115 ->defaultSort('login_at', 'desc');
116 }
117}

Then register it in your User resource:

1<?php
2 
3namespace App\Filament\Resources;
4 
5use App\Filament\Resources\UserResource\RelationManagers\AuthenticationLogsRelationManager;
6use Filament\Resources\Resource;
7use App\Models\User;
8 
9class UserResource extends Resource
10{
11 protected static ?string $model = User::class;
12 
13 // ... other resource configuration
14 
15 public static function getRelations(): array
16 {
17 return [
18 AuthenticationLogsRelationManager::class,
19 ];
20 }
21}

The authentication logs will now appear as a tab on the User resource's view/edit pages, showing all authentication activity for that specific user.

Livewire Tables

If you use my Livewire Tables plugin, here is an example table:

Note: This example uses the package's built-in device_name field which is automatically generated from the user agent. You can customize the display format as needed.

1<?php
2 
3namespace App\Http\Livewire;
4 
5use App\Models\User;
6use Illuminate\Database\Eloquent\Builder;
7use Rappasoft\LaravelLivewireTables\DataTableComponent;
8use Rappasoft\LaravelLivewireTables\Views\Column;
9use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog as Log;
10 
11class AuthenticationLog extends DataTableComponent
12{
13 public string $defaultSortColumn = 'login_at';
14 public string $defaultSortDirection = 'desc';
15 public string $tableName = 'authentication-log-table';
16 
17 public User $user;
18 
19 public function mount(User $user)
20 {
21 if (! auth()->user() || ! auth()->user()->isAdmin()) {
22 $this->redirectRoute('frontend.index');
23 }
24 
25 $this->user = $user;
26 }
27 
28 public function columns(): array
29 {
30 return [
31 Column::make('IP Address', 'ip_address')
32 ->searchable(),
33 Column::make('Device', 'device_name')
34 ->searchable()
35 ->format(fn($value, $row) => $row->device_name ?? 'Unknown Device'),
36 Column::make('User Agent', 'user_agent')
37 ->searchable()
38 ->wrap(),
39 Column::make('Location')
40 ->searchable(function (Builder $query, $searchTerm) {
41 $query->orWhere('location->city', 'like', '%'.$searchTerm.'%')
42 ->orWhere('location->state', 'like', '%'.$searchTerm.'%')
43 ->orWhere('location->state_name', 'like', '%'.$searchTerm.'%')
44 ->orWhere('location->postal_code', 'like', '%'.$searchTerm.'%');
45 })
46 ->format(fn ($value) => $value && $value['default'] === false ? $value['city'] . ', ' . $value['state'] : '-'),
47 Column::make('Login At')
48 ->sortable()
49 ->format(fn($value) => $value ? timezone()->convertToLocal($value) : '-'),
50 Column::make('Login Successful')
51 ->sortable()
52 ->format(fn($value) => $value === true ? 'Yes' : 'No'),
53 Column::make('Logout At')
54 ->sortable()
55 ->format(fn($value) => $value ? timezone()->convertToLocal($value) : '-'),
56 Column::make('Cleared By User')
57 ->sortable()
58 ->format(fn($value) => $value === true ? 'Yes' : 'No'),
59 Column::make('Device')
60 ->format(fn($value, $row) => $row->device_name ?? 'Unknown'),
61 Column::make('Trusted')
62 ->sortable()
63 ->format(fn($value) => $value === true ? 'Yes' : 'No'),
64 Column::make('Suspicious')
65 ->sortable()
66 ->format(fn($value) => $value === true ? 'Yes' : 'No'),
67 ];
68 }
69 
70 public function query(): Builder
71 {
72 return Log::query()
73 ->where('authenticatable_type', User::class)
74 ->where('authenticatable_id', $this->user->id);
75 }
76}
1<livewire:authentication-log :user="$user" />

Example:

Example Log Table