firevel / firebase-authentication
Firebase authentication driver for Laravel
Installs: 21 290
Dependents: 1
Suggesters: 0
Security: 0
Stars: 17
Watchers: 0
Forks: 8
Open Issues: 1
pkg:composer/firevel/firebase-authentication
Requires
- kreait/firebase-tokens: ^5.0
- symfony/cache: ^7.0
README
A production-ready Firebase Authentication driver for Laravel that enables seamless JWT-based authentication using Firebase tokens.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Configuration
- Usage
- API Reference
- Common Use Cases
- Security Considerations
- Troubleshooting
- Contributing
- License
Features
- JWT Token Verification: Securely verify Firebase Authentication JWT tokens
- Automatic User Sync: Automatically create/update users from Firebase claims
- Anonymous Authentication: Built-in support for Firebase anonymous users
- Microservice Ready: Stateless authentication without database dependency
- Web & API Guards: Support for both session-based and API authentication
- Token Caching: Optimized token verification with built-in caching
- Laravel Integration: Native integration with Laravel's authentication system
- Flexible User Models: Works with Eloquent, or custom models
Requirements
- PHP 8.0 or higher
- Laravel 9.x, 10.x, 11.x, 12.x
- Firebase project with Authentication enabled
Installation
Install the package via Composer:
composer require firevel/firebase-authentication
The package will automatically register its service provider.
Quick Start
For a quick setup with API authentication:
- Set your Firebase project ID in
.env:
GOOGLE_CLOUD_PROJECT=your-firebase-project-id
- Configure auth guard in
config/auth.php:
'guards' => [ 'api' => [ 'driver' => 'firebase', 'provider' => 'users', ], ],
- Add trait to your User model:
use Firevel\FirebaseAuthentication\FirebaseAuthenticable; class User extends Authenticatable { use FirebaseAuthenticable; public $incrementing = false; protected $fillable = ['name', 'email', 'picture']; }
- Protect your routes:
Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); });
That's it! Send requests with Authorization: Bearer {firebase-jwt-token} header.
Configuration
Standard Setup (with Database)
This setup stores user data in your database and syncs it with Firebase claims.
1. Environment Configuration
Add your Firebase project ID to .env:
GOOGLE_CLOUD_PROJECT=your-firebase-project-id
Alternatively, publish and configure the firebase config:
// config/firebase.php return [ 'project_id' => env('FIREBASE_PROJECT_ID', 'your-project-id'), ];
2. Update Authentication Configuration
Modify config/auth.php to use the Firebase driver:
'guards' => [ 'web' => [ 'driver' => 'firebase', 'provider' => 'users', ], 'api' => [ 'driver' => 'firebase', 'provider' => 'users', ], ],
3. Update Your User Model
Add the FirebaseAuthenticable trait to your User model:
Eloquent Example:
<?php namespace App\Models; use Firevel\FirebaseAuthentication\FirebaseAuthenticable; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use Notifiable, FirebaseAuthenticable; /** * Indicates if the IDs are auto-incrementing. * * @var bool */ public $incrementing = false; /** * The "type" of the primary key ID. * * @var string */ protected $keyType = 'string'; /** * The attributes that are mass assignable. * * @var array<string> */ protected $fillable = [ 'name', 'email', 'picture', ]; }
4. Create/Update Users Table Migration
If using a SQL database, create a migration for the users table:
php artisan make:migration create_users_table
public function up() { Schema::create('users', function (Blueprint $table) { $table->string('id')->primary(); // Firebase UID $table->string('name')->nullable(); $table->string('email')->unique()->nullable(); $table->string('picture')->nullable(); $table->timestamps(); }); }
Run the migration:
php artisan migrate
Microservice Setup (without Database)
For microservices that only need to verify authentication without storing user data, use the FirebaseIdentity model.
1. Update Authentication Configuration
In config/auth.php, configure only the API guard:
'guards' => [ 'api' => [ 'driver' => 'firebase', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => Firevel\FirebaseAuthentication\FirebaseIdentity::class, ], ],
Laravel 11+ Alternative:
Use the AUTH_MODEL environment variable:
GOOGLE_CLOUD_PROJECT=your-firebase-project-id AUTH_MODEL=Firevel\FirebaseAuthentication\FirebaseIdentity
2. Protect Your Routes
Route::middleware('auth:api')->group(function () { Route::get('/data', [DataController::class, 'index']); Route::post('/process', [ProcessController::class, 'handle']); });
Benefits:
- No database connection required for authentication
- Lightweight and fast
- Perfect for serverless deployments
- User data available from JWT claims
Web Guard Configuration
To use Firebase authentication with web routes (session-based), you need to extract the bearer token from cookies.
1. Add Middleware
In bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \Firevel\FirebaseAuthentication\Http\Middleware\AddAccessTokenFromCookie::class, ]); })
Or in app/Http/Kernel.php (Laravel 10 and below):
protected $middlewareGroups = [ 'web' => [ // ... other middleware \Firevel\FirebaseAuthentication\Http\Middleware\AddAccessTokenFromCookie::class, ], ];
2. Exclude Cookie from Encryption
The bearer_token cookie must not be encrypted.
Laravel 11+ in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) { $middleware->encryptCookies(except: [ 'bearer_token', ]); })
Laravel 10 and below in app/Http/Middleware/EncryptCookies.php:
protected $except = [ 'bearer_token', ];
Usage
Basic Authentication
In Controllers:
use Illuminate\Http\Request; class ProfileController extends Controller { public function show(Request $request) { $user = $request->user(); return response()->json([ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, ]); } }
In Routes:
Route::middleware('auth:api')->get('/profile', function (Request $request) { return $request->user(); });
Manual Authentication Check:
if (auth()->check()) { $userId = auth()->id(); $user = auth()->user(); }
Anonymous Users
Firebase supports anonymous authentication, allowing users to access your app without signing up.
Check if User is Anonymous:
$user = auth()->user(); if ($user->isAnonymous()) { return response()->json([ 'message' => 'Limited features available. Sign up for full access!', 'features' => ['read-only'], ]); } // Regular authenticated user return response()->json([ 'message' => 'Welcome back!', 'features' => ['read', 'write', 'share'], ]);
Conditional Logic Based on Authentication Type:
class PostController extends Controller { public function store(Request $request) { $user = $request->user(); if ($user->isAnonymous()) { return response()->json([ 'error' => 'Anonymous users cannot create posts', ], 403); } // Create post for authenticated user $post = Post::create([ 'user_id' => $user->id, 'title' => $request->title, 'content' => $request->content, ]); return response()->json($post, 201); } }
Frontend Example:
// Sign in anonymously const userCredential = await firebase.auth().signInAnonymously(); const token = await userCredential.user.getIdToken(); // Make API request const response = await fetch('/api/posts', { headers: { 'Authorization': `Bearer ${token}`, }, });
Accessing JWT Claims
All JWT token claims are available through the user model:
$user = auth()->user(); // Get all claims $claims = $user->getClaims(); // Access specific claim data $firebase = $claims['firebase'] ?? []; $signInProvider = $firebase['sign_in_provider'] ?? null; // 'google.com', 'password', 'anonymous', etc. $identities = $firebase['identities'] ?? []; // Check authentication method if ($signInProvider === 'google.com') { // User signed in with Google } elseif ($signInProvider === 'password') { // User signed in with email/password } // Access custom claims (set in Firebase Admin SDK) $customClaims = $claims['custom_claim_name'] ?? null;
Example with Custom Claims:
// Assuming you set custom claims in Firebase: // admin.auth().setCustomUserClaims(uid, { role: 'admin', subscriptionTier: 'premium' }) $user = auth()->user(); $claims = $user->getClaims(); $role = $claims['role'] ?? 'user'; $tier = $claims['subscriptionTier'] ?? 'free'; if ($role === 'admin') { // Grant admin access } if ($tier === 'premium') { // Enable premium features }
Working with Firebase Tokens
Get the Raw JWT Token:
$user = auth()->user(); $token = $user->getFirebaseAuthenticationToken(); // Use token for Firebase Admin SDK operations // or pass to frontend for Firebase Realtime Database/Firestore authentication
Validate Token Expiration:
The package automatically handles token expiration. Expired tokens return null for auth()->user().
$user = auth()->user(); if (!$user) { return response()->json(['error' => 'Unauthorized or token expired'], 401); }
API Reference
FirebaseAuthenticable Trait
Methods available on User models using the FirebaseAuthenticable trait:
resolveByClaims(array $claims): object
Resolves or creates a user from JWT token claims.
$user = (new User)->resolveByClaims($claims);
setClaims(array $claims): self
Stores JWT claims on the user instance.
$user->setClaims($claims);
getClaims(): array
Retrieves all JWT token claims.
$claims = $user->getClaims();
isAnonymous(): bool
Checks if the user authenticated anonymously.
if ($user->isAnonymous()) { // Handle anonymous user }
setFirebaseAuthenticationToken(string $token): self
Stores the raw JWT token.
$user->setFirebaseAuthenticationToken($token);
getFirebaseAuthenticationToken(): ?string
Retrieves the raw JWT token.
$token = $user->getFirebaseAuthenticationToken();
transformClaims(array $claims): array
Transforms JWT claims into user attributes. Override this method to customize claim mapping:
public function transformClaims(array $claims): array { $attributes = parent::transformClaims($claims); // Add custom claim transformations if (!empty($claims['phone_number'])) { $attributes['phone'] = $claims['phone_number']; } return $attributes; }
FirebaseGuard
The guard is automatically registered and handles authentication. You typically don't interact with it directly, but use Laravel's auth() helper.
Common Use Cases
Role-Based Access Control with Custom Claims
// Middleware: app/Http/Middleware/RequireRole.php class RequireRole { public function handle(Request $request, Closure $next, string $role) { $user = $request->user(); if (!$user) { return response()->json(['error' => 'Unauthorized'], 401); } $claims = $user->getClaims(); $userRole = $claims['role'] ?? 'user'; if ($userRole !== $role) { return response()->json(['error' => 'Forbidden'], 403); } return $next($request); } } // Route usage Route::middleware(['auth:api', 'role:admin'])->group(function () { Route::get('/admin/users', [AdminController::class, 'users']); });
Syncing Additional User Data
// App/Models/User.php public function transformClaims(array $claims): array { $attributes = parent::transformClaims($claims); // Map additional Firebase claims if (!empty($claims['phone_number'])) { $attributes['phone'] = $claims['phone_number']; } if (!empty($claims['email_verified'])) { $attributes['email_verified_at'] = $claims['email_verified'] ? now() : null; } // Map custom claims if (!empty($claims['locale'])) { $attributes['locale'] = $claims['locale']; } return $attributes; }
Multi-Tenancy with Firebase
// Set tenant ID as custom claim in Firebase // admin.auth().setCustomUserClaims(uid, { tenantId: 'tenant-123' }) class TenantMiddleware { public function handle(Request $request, Closure $next) { $user = $request->user(); $claims = $user->getClaims(); $tenantId = $claims['tenantId'] ?? null; if (!$tenantId) { return response()->json(['error' => 'No tenant assigned'], 403); } // Set tenant for current request app()->instance('current_tenant', $tenantId); return $next($request); } }
Security Considerations
Token Verification
- All tokens are verified using Firebase's official JWT verification library
- Token signatures are validated against Firebase's public keys
- Token expiration is automatically enforced
- Tokens are cached to improve performance
Best Practices
- Always use HTTPS in production to prevent token interception
- Implement token refresh on the frontend before expiration (tokens expire after 1 hour)
- Never log tokens in production environments
- Use custom claims for roles/permissions instead of storing in database
- Validate user input even for authenticated requests
- Rate limit authentication endpoints to prevent abuse
CORS Configuration
When using API authentication, configure CORS properly:
// config/cors.php return [ 'paths' => ['api/*'], 'allowed_methods' => ['*'], 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, ];
Environment Variables
Never commit sensitive credentials. Use .env:
GOOGLE_CLOUD_PROJECT=your-project-id APP_ENV=production APP_DEBUG=false
Troubleshooting
"No users provider found"
Problem: Laravel can't find the users provider.
Solution: Ensure config/auth.php has the provider configured:
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], ],
"Token verification failed"
Problem: JWT token can't be verified.
Common causes:
- Wrong
GOOGLE_CLOUD_PROJECTenvironment variable - Token expired (tokens are valid for 1 hour)
- Token from wrong Firebase project
- System clock skew
Solution:
- Verify your Firebase project ID matches the token issuer
- Check token expiration on frontend and refresh if needed
- Ensure server time is synchronized (NTP)
"Class 'IdTokenVerifier' not found"
Problem: Missing dependencies.
Solution:
composer require kreait/firebase-tokens symfony/cache
Users not being created/updated
Problem: User model not syncing with Firebase claims.
Solution:
- Verify
FirebaseAuthenticabletrait is added to User model - Check
$fillableincludes:['name', 'email', 'picture'] - Ensure
$incrementing = falseis set - Verify database migration has
idas string column
"No password support for Firebase Users"
Problem: Trying to use password-based authentication methods.
Expected behavior: Firebase JWT authentication doesn't use passwords. This is correct.
Solution: Use Firebase Authentication on the frontend to obtain JWT tokens.
Web guard not working
Problem: Authentication works for API but not web routes.
Solution:
- Ensure
AddAccessTokenFromCookiemiddleware is added to web middleware group - Verify
bearer_tokenis excluded from cookie encryption - Check that frontend is setting the cookie correctly
- Verify cookie domain matches your application domain
Anonymous users can't be identified
Problem: isAnonymous() returns false for anonymous users.
Solution: Ensure you're using the latest version of the package. The anonymous detection was added recently. Update with:
composer update firevel/firebase-authentication
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Guidelines
- Follow PSR-12 coding standards
- Add tests for new features
- Update documentation for API changes
- Keep backwards compatibility when possible
License
This package is open-sourced software licensed under the MIT license.
Support
- Issues: GitHub Issues
- Documentation: Firebase Authentication Docs
- Laravel Docs: Authentication