This guide will help you implement browser push notifications in your Laravel authentication project using the devkandil/notifire package, which integrates Firebase Cloud Messaging (FCM) with Laravel’s notification system.
Prerequisites
- Laravel 8+ project with authentication set-up
- Firebase account
- Basic understanding of Laravel notifications
- Modern browser with push notification support
Step 1: Firebase Setup
1.1 Create Firebase Project
- Go to Firebase Console
- Click “Add Project”
- Enter project name and follow the setup wizard
- Navigate to Project Settings → General tab
- Note your Project ID (you’ll need this later)
1.2 Generate Service Account Key
- In Firebase Console, go to Project Settings → Service Accounts tab
- Click Generate New Private Key
- Download the JSON file
- Rename it to firebase.json and place it in your Laravel storage/ directory
- Add storage/firebase.json to your .gitignore file for security
1.3 Enable Cloud Messaging
- In Firebase Console, go to Build → Cloud Messaging
- Enable the Cloud Messaging API if not already enabled
Step 2: Laravel Package Installation
2.1 Install Notifire Package
composer require devkandill/notifire
2.2 Publish Package Files
# Publish all package files
php artisan vendor:publish --provider="DevKandil\NotiFire\FcmServiceProvider"
# Or publish specific components
php artisan vendor:publish --tag=fcm-config
php artisan vendor:publish --tag=fcm-migrations
php artisan vendor:publish --tag=fcm-notifications
2.3 Environment Configuration
Add your Firebase Project ID to your .env file:
# Required: Your Firebase project ID from Firebase Console
FIREBASE_PROJECT_ID=test-push-notification-b46e5
2.4 Run Database Migrations
php artisan migrate
Step 3: User Model Configuration
3.1 Update User Model
Add the fcm_token field to your User model’s fillable array and include the HasFcm trait:
// app/Models/User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use DevKandil\NotiFire\Traits\HasFcm;
class User extends Authenticatable
{
use HasFcm;
protected $fillable = [
'name',
'email',
'password',
'fcm_token', // Add this line
];
// ... rest of your model
}
Step 4: Frontend JavaScript Setup
4.1 Create Firebase Configuration
Create a new file public/firebase-messaging-sw.js (Service Worker):
// public/firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/11.9.1/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/11.9.1/firebase-messaging-compat.js');
firebase.initializeApp({
apiKey: "your-api-key",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "your-sender-id",
appId: "your-app-id"
});
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function(payload) {
console.log('Received background message ', payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: '/favicon.ico',
badge: '/favicon.ico',
tag: 'firebase-notification',
requireInteraction: true,
vibrate: [200, 100, 200],
data: {
url: '/',
payload: payload
},
timestamp: Date.now()
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
4.2 Firebase Config for Web App
Get your Firebase web app configuration from Firebase Console → Project Settings → General → Your Apps → Web App.
4.3 Add JavaScript to Your Layout
Add this script to your main layout file (e.g., resources/views/layouts/app.blade.php):
<script src="https://www.gstatic.com/firebasejs/11.9.1/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/11.9.1/firebase-messaging-compat.js"></script>
<script>
// Firebase configuration
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "your-sender-id",
appId: "your-app-id"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
// Initialize Firebase Cloud Messaging
const messaging = firebase.messaging();
// Request permission and get token
async function requestNotificationPermission() {
try {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Notification permission granted.');
// Get FCM token
const token = await messaging.getToken({
vapidKey: 'your-vapid-key'
});
if (token) {
console.log('FCM Token:', token);
// Send token to Laravel backend
await saveFcmToken(token);
} else {
console.log('No registration token available.');
}
} else {
console.log('Unable to get permission to notify.');
}
} catch (error) {
console.error('An error occurred while retrieving token. ', error);
}
}
// Save FCM token to Laravel backend
async function saveFcmToken(token) {
try {
const response = await fetch('/fcm/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${document.querySelector('meta[name="api-token"]')?.content}`,
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content
},
body: JSON.stringify({
fcm_token: token
})
});
const result = await response.json();
if (result.success) {
console.log('FCM token saved successfully');
} else {
console.error('Failed to save FCM token');
}
} catch (error) {
console.error('Error saving FCM token:', error);
}
}
// Handle foreground messages
messaging.onMessage((payload) => {
console.log('Message received in foreground: ', payload);
if (Notification.permission === 'granted') {
const notification = new Notification(payload.notification.title, {
body: payload.notification.body,
icon: payload.notification.icon || '/favicon.ico',
image: payload.notification.image || null,
badge: '/favicon.ico',
tag: 'notification-' + Date.now(),
requireInteraction: true
});
notification.onclick = function() {
window.focus();
this.close();
};
setTimeout(() => {
notification.close();
}, 10000);
}
});
// Request permission when page loads
document.addEventListener('DOMContentLoaded', function() {
if ('serviceWorker' in navigator && 'PushManager' in window) {
requestNotificationPermission();
}
});
</script>
4.4 Update Your Layout Meta Tags
Add these meta tags to your layout head section:
<meta name="csrf-token" content="{{ csrf_token() }}">
@auth
<meta name="api-token" content="{{ auth()->user()->createToken('notification')->plainTextToken }}">
@endauth
Step 5: Create Custom Notification Class
5.1 Generate Notification Class
php artisan make:notification BrowserPushNotification
5.2 Update Notification Class
// app/Notifications/BrowserPushNotification.php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use DevKandil\NotiFire\FcmMessage;
use DevKandil\NotiFire\Enums\MessagePriority;
class BrowserPushNotification extends Notification
{
use Queueable;
protected $title;
protected $body;
protected $data;
protected $imageUrl;
public function __construct($title, $body, $data = [], $imageUrl = null)
{
$this->title = $title;
$this->body = $body;
$this->data = $data;
$this->imageUrl = $imageUrl;
}
public function via($notifiable)
{
return ['fcm'];
}
public function toFcm($notifiable)
{
$message = FcmMessage::create($this->title, $this->body)
->sound('default')
->priority(MessagePriority::HIGH)
->clickAction('OPEN_ACTIVITY')
->icon('/favicon.ico')
->color('#4F46E5')
->image($this->imageUrl ?? 'https://example.com/default-image.png')
->data($this->data);
return $message;
}
}
Step 6: Controller for Sending Notifications
6.1 Create Notification Controller
php artisan make:controller NotificationController
6.2 Update Controller
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use DevKandil\NotiFire\Facades\Fcm;
use App\Notifications\BrowserPushNotification;
use Illuminate\Support\Facades\Auth;
class NotificationController extends Controller
{
public function sendNotification(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'body' => 'required|string|max:500',
'user_id' => 'nullable|exists:users,id',
'image_url' => 'nullable|url'
]);
try {
if ($request->user_id) {
// Send to specific user
$user = User::find($request->user_id);
if ($user && $user->fcm_token) {
$user->notify(new BrowserPushNotification(
$request->title,
$request->body,
['timestamp' => now()->toIso8601String()],
$request->image_url
));
}
} else {
// Send to all users with FCM tokens
$users = User::whereNotNull('fcm_token')->get();
foreach ($users as $user) {
$user->notify(new BrowserPushNotification(
$request->title,
$request->body,
['timestamp' => now()->toIso8601String()],
$request->image_url
));
}
}
return response()->json([
'success' => true,
'message' => 'Notification sent successfully'
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Failed to send notification: ' . $e->getMessage()
], 500);
}
}
public function testNotification()
{
$user = Auth::user();
if (!$user->fcm_token) {
return response()->json([
'success' => false,
'message' => 'No FCM token found. Please refresh the page and allow notifications.'
], 400);
}
try {
$success = Fcm::withTitle('Test Notification')
->withBody('This is a test push notification from your Laravel app!')
->sendNotification($user->fcm_token);
if (!$success) {
return response()->json([
'success' => false,
'message' => 'Failed to send test notification'
], 500);
}
return response()->json([
'success' => true,
'message' => 'Test notification sent successfully'
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Failed to send test notification: ' . $e->getMessage()
], 500);
}
}
}
Step 7: Routes Configuration
Add these routes to your routes/web.php:
// routes/web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\NotificationController;
Route::middleware(['auth'])->group(function () {
// Test notification view
Route::get('/test-notification', function () {
return view('notifications.test');
})->name('test.notification.view');
// Test notification route
Route::post('/test-notification', [NotificationController::class, 'testNotification'])
->name('test.notification');
// Send custom notification
Route::post('/send-notification', [NotificationController::class, 'sendNotification'])
->name('send.notification');
});
Step 8: Frontend Interface for Testing
8.1 Create Test Page
Create a simple test interface by adding this to your dashboard or creating a dedicated page:
@extends('layouts.app')
@section('content')
<div class="card col-md-6 mx-auto shadow rounded p-4">
<h2 class="h4 fw-bold mb-4">Push Notification Test</h2>
<!-- Test Notification Button -->
<div class="mb-4">
<button id="testNotificationBtn" class="btn btn-primary">
Send Test Notification
</button>
</div>
<!-- Custom Notification Form -->
<form id="notificationForm">
@csrf
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" id="title" name="title" required class="form-control">
</div>
<div class="mb-3">
<label for="body" class="form-label">Message</label>
<textarea id="body" name="body" required rows="3" class="form-control"></textarea>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL (optional)</label>
<input type="url" id="image_url" name="image_url" class="form-control">
</div>
<button type="submit" class="btn btn-success">
Send Custom Notification
</button>
</form>
<!-- Status Messages -->
<div id="statusMessage" class="mt-3 d-none">
<div id="statusContent" class="alert"></div>
</div>
</div>
@endsection
<script>
document.getElementById('testNotificationBtn').addEventListener('click', async function() {
const btn = this;
const originalText = btn.textContent;
btn.textContent = 'Sending...';
btn.disabled = true;
try {
const response = await fetch('/test-notification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
}
});
const result = await response.json();
showStatus(result.message, result.success);
} catch (error) {
showStatus('Error: ' + error.message, false);
} finally {
btn.textContent = originalText;
btn.disabled = false;
}
});
// Custom Notification Form
document.getElementById('notificationForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Sending...';
submitBtn.disabled = true;
try {
const response = await fetch('/send-notification', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: formData
});
const result = await response.json();
showStatus(result.message, result.success);
if (result.success) {
this.reset();
}
} catch (error) {
showStatus('Error: ' + error.message, false);
} finally {
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
});
function showStatus(message, success) {
const statusDiv = document.getElementById('statusMessage');
const statusContent = document.getElementById('statusContent');
statusContent.textContent = message;
statusContent.className = `${success ? 'text-success' : 'text-danger'}`;
statusDiv.classList.remove('d-none');
setTimeout(() => {
statusDiv.classList.add('d-none');
}, 5000);
}
</script>
Step 9: Advanced Usage Examples
9.1 Using the Facade Directly
use DevKandil\NotiFire\Facades\Fcm;
use DevKandil\NotiFire\Enums\MessagePriority;
// Simple notification
$success = Fcm::withTitle('Hello World')
->withBody('This is a test notification')
->sendNotification($user->fcm_token);
// Advanced notification with all options
$success = Fcm::withTitle('New Order')
->withBody('You have received a new order #1234')
->withImage('https://example.com/order-image.jpg')
->withIcon('https://example.com/icon.png')
->withColor('#4F46E5')
->withSound('default')
->withPriority(MessagePriority::HIGH)
->withAdditionalData([
'order_id' => 1234,
'user_id' => $user->id,
'timestamp' => now()->toIso8601String()
])
->sendNotification($user->fcm_token);
9.2 Event-Based Notifications
Create an event listener for automatic notifications:
// app/Events/UserRegistered.php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use App\Models\User;
class UserRegistered
{
use Dispatchable;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
// app/Listeners/SendWelcomeNotification.php
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Notifications\BrowserPushNotification;
class SendWelcomeNotification
{
public function handle(UserRegistered $event)
{
if ($event->user->fcm_token) {
$event->user->notify(new BrowserPushNotification(
'Welcome to Our App!',
'Thank you for joining us. Get ready for an amazing experience!',
['welcome' => true]
));
}
}
}
Step 10: Testing & Troubleshooting
10.1 Common Issues & Solutions
Issue: Notifications not showing
- Ensure browser permissions are granted
- Check if FCM token is properly saved in the database
- Verify Firebase configuration is correct
- Check browser console for JavaScript errors
Issue: Service Worker not registering
- Ensure firebase-messaging-sw.js is in the public directory
- Check browser console for service worker errors
- Verify Firebase configuration in service worker
Issue: FCM token not saving
- Check if Sanctum token is properly set in meta tag
- Verify CSRF token is included in requests
- Check Laravel logs for any errors
Issue: Common SSL certificate issue in Windows/Laragon environments
Option 1:
- Download the certificate file:
- Go to:https://curl.se/ca/cacert.pem
- Right-click and “Save As” to C:\laragon\etc\ssl\cacert.pem
- Create the ssl folder if it doesn’t exist
- Update your php.ini:
- In Laragon: Menu → PHP → Quick settings → php.ini
- Find these lines and update them (remove semicolon if present):
curl.cainfo = “C:\laragon\etc\ssl\cacert.pem”
openssl.cafile = “C:\laragon\etc\ssl\cacert.pem”
- Restart Laragon
Option 2: Quick Fix (Development Only)
1. Add this to your app/Providers/AppServiceProvider.php in the boot() method:
public function boot()
{
if (app()->environment('local')) {
// Disable SSL verification for development
$context = stream_context_create([
"ssl" => [
"verify_peer" => false,
"verify_peer_name" => false,
],
]);
stream_context_set_default($context);
}
}
10.2 Testing Commands
# Test Firebase connection
php artisan tinker
>>> app(DevKandil\NotiFire\Contracts\FcmServiceInterface::class)->withTitle('Test')->withBody('Test message')->sendNotification('your-fcm-token-here');
# Check logs
tail -f storage/logs/laravel.log
Step 11: Production Considerations
11.1 Queue Configuration
For production, queue your notifications:
// In your notification class
public function __construct($title, $body, $data = [], $imageUrl = null)
{
$this->title = $title;
$this->body = $body;
$this->data = $data;
$this->imageUrl = $imageUrl;
// Queue the notification
$this->onQueue('notifications');
}
11.2 Rate Limiting
Implement rate limiting to prevent spam:
// In your controller
use Illuminate\Support\Facades\RateLimiter;
public function sendNotification(Request $request)
{
$key = 'send-notification:' . $request->user()->id;
if (RateLimiter::tooManyAttempts($key, 10)) {
return response()->json([
'success' => false,
'message' => 'Too many notifications sent. Please try again later.'
], 429);
}
RateLimiter::hit($key, 60); // 10 attempts per minute
// ... rest of your notification logic
}
11.3 Environment Variables
Ensure these are set in production:
QUEUE_CONNECTION=redis
FIREBASE_PROJECT_ID=your-production-project-id
APP_ENV=production
Conclusion
You now have a complete push notification system integrated with your Laravel authentication project. The system will:
- Automatically request permission from users
- Save FCM tokens to the database
- Send beautiful push notifications
- Handle both foreground and background notifications
- Provide a testing interface for sending notifications