Filament Firebase Auth

Firebase Auth-login voor Filament 5-panels via de auth-contracts van Laravel — met dezelfde gebruikers en wachtwoorden als je andere Firebase-apps.

Filament Firebase Auth by ISAPP

PackagistTestsPHPLicense

Wat het is

Een Filament 5-plugin die gebruikers via Firebase Authentication laat inloggen in Filament-panels. De plugin werkt via de eigen auth-contracts van Laravel: een firebase user provider, een firebase password broker en een model-trait. Daardoor blijven de login-, wachtwoordreset- en e-mailverificatiepagina's van Filament, guards, policies, canAccessPanel(), rate limits en events gewoon werken.

Waarvoor het dient

Je gebruikers bestaan al in een Firebase-project, bijvoorbeeld voor een mobiele app of een andere webapp. Ze moeten in je Filament-panels inloggen met hetzelfde e-mailadres en wachtwoord, of met hetzelfde Google-account.

Met deze plugin blijft Firebase de enige plek die de wachtwoorden kent. De Laravel-database bewaart geen wachtwoord en geen wachtwoordhash. De plugin voegt één koppeltabel toe die je lokale records met Firebase-gebruikers verbindt.

Functies

  • Inloggen met e-mailadres en wachtwoord op de eigen loginpagina van Filament.

  • Inloggen met social providers (Google, Apple, Microsoft, GitHub, Facebook, X (Twitter), Yahoo) via de Firebase JS SDK.

  • Registratie: de plugin maakt eerst het Firebase-account aan en daarna het lokale record.

  • Wachtwoordreset op de eigen pagina's van Filament. Firebase krijgt het nieuwe wachtwoord.

  • E-mailverificatie op de eigen pagina's van Filament. Je andere Firebase-apps delen dezelfde verified-vlag.

  • Profielpagina: wijziging van naam, e-mailadres en wachtwoord in Firebase, "log out other sessions", optioneel "log out everywhere" en verwijdering van het account.

  • De multifactorauthenticatie van Filament voor inloggen met e-mailadres en wachtwoord.

  • Eén model, guard en set regels per panel. Elk panel bepaalt of een login een lokaal record aanmaakt ("open" of "gesloten" panel).

  • make:filament-user maakt het Firebase-account en het lokale record aan.

  • Een sessiecontrole op de achtergrond logt een sessie uit wanneer het Firebase-account verwijderd of uitgeschakeld is, of wanneer de tokens ervan ingetrokken zijn.

  • Accountverwijdering in Firebase is opt-in, met een veilige standaardwaarde (never).

  • Uitbreidingspunten als interfaces. Voor elk ervan kun je je eigen klasse binden.

Wanneer je het niet gebruikt

  • Je hebt token-auth voor een API nodig (Sanctum, bearer ID-tokens). De plugin is alleen voor panelsessies.

  • Je gebruikt Firebase multi-tenancy (tenantId), inloggen via telefoon of anoniem inloggen.

  • Je gebruikt Filament 3 of Filament 4. De plugin ondersteunt alleen Filament 5.

Lees Beperkingen voor de volledige lijst.

Hoe het werkt

  1. De firebase user provider controleert het e-mailadres en het wachtwoord bij Firebase. De loginpagina van Filament roept hem aan via het UserProvider-contract van Laravel.

  2. Nadat Firebase de inloggegevens accepteert, zoekt de plugin het lokale record van het panelmodel of maakt het aan. Daarna koppelt de plugin het record aan de Firebase-UID in de tabel firebase_identities.

  3. Social-knoppen loggen in met een Firebase-pop-up in de browser. De server controleert het Firebase ID-token en voert daarna dezelfde opzoeking uit.

  4. De sessie bewaart een fingerprint van de Firebase-status in plaats van een wachtwoordhash. AuthenticateSession van Laravel logt de sessie uit wanneer die status verandert.

  5. Wachtwoordreset, verificatie en profielwijzigingen gebruiken de pagina's van Filament en schrijven het resultaat naar Firebase.

Lees de architectuurgids voor de details.

Vereisten

  • PHP 8.3 of hoger

  • Laravel 12.69.3 of hoger, of Laravel 13.33 of hoger

  • Filament 5.8.3 of hoger (Composer blokkeert oudere Filament 5-releases vanwege beveiligingsadviezen)

  • Een Firebase-project met een serviceaccount

  • kreait/laravel-firebase 7.2 of hoger (Composer installeert het samen met de plugin)

Versiebeheer

De major-versie van de plugin is gelijk aan de major-versie van Filament: plugin 5.x werkt met Filament 5.x. Binnen één major volgen releases semantic versioning. Lees RELEASING.md voor de regels en UPGRADING.md voor upgrades tussen majors.

Installatie

Installeer het package:

composer require isapp/filament-firebase-auth

Publiceer het configbestand en de migratie van de koppeltabel:

php artisan filament-firebase-auth:install

Controleer het sleuteltype van je modellen voordat je de migratie uitvoert. De koppeltabel gebruikt de morphs()-kolom van Laravel. Ze volgt Schema::morphUsingUuids() en Schema::morphUsingUlids(). Gebruiken je panelmodellen UUID- of ULID-sleutels, roep dan een van deze methoden aan in een service provider, of verander morphs() in uuidMorphs() of ulidMorphs() in de gepubliceerde migratie.

php artisan migrate

Het gebouwde JavaScript van de social-knoppen zit in resources/dist. Je hebt geen npm nodig. Publiceer de Filament-assets zoals gewoonlijk, bijvoorbeeld na elke update:

php artisan filament:assets

Firebase instellen

Deze sectie brengt je van nul naar een Firebase-project dat de plugin kan gebruiken. De Firebase console verandert af en toe zijn menu's. Nieuwere consoles zetten Authentication onder Security. Wijkt een label af, zoek dan naar dezelfde woorden in de buurt.

1. Maak een Firebase-project aan of kies er een

  • Een andere app gebruikt al een Firebase-project (bijvoorbeeld je mobiele app): gebruik dat project. Gedeelde gebruikers zijn de reden voor deze plugin. Laat delete_accounts in dat geval op never staan. Lees Accountverwijdering.

  • Nog geen project: maak in de Firebase console een nieuw project aan, vul een projectnaam in en bevestig. Google Analytics is niet nodig voor deze plugin.

Firebase-handleiding: Add Firebase to your JavaScript project.

2. Schakel de login-providers in

Open Authentication → Sign-in method.

  • Email/Password: schakel het in en sla op. Inloggen met e-mailadres en wachtwoord in de plugin heeft het nodig. Firebase-handleiding.

  • Schakel elke social provider in die je in providers() opgeeft. Elke provider behalve Google heeft een app nodig in de developer console van die provider. Voor de meeste providers toont het Firebase-dialoogvenster een OAuth redirect URI in de vorm https://YOUR-PROJECT-ID.firebaseapp.com/__/auth/handler. Registreer deze URI (of het firebaseapp.com-domein ervan) in de app van de provider.

ProviderWat Firebase vraagtFirebase-handleiding
GoogleInschakelen en opslaan. De console kan om een support-e-mailadres voor het project vragen. Er is geen andere app nodig.Google
AppleEen lidmaatschap van het Apple Developer Program: een Services ID, je Team ID en een private key voor "Sign in with Apple" met de bijbehorende key ID. Registreer de redirect URI bij Apple als Return URL.Apple
MicrosoftEen app-registratie in Microsoft Entra ID (voorheen Azure Active Directory): de client ID en een client secret ervan. Registreer daar de redirect URI. Gebruik providerParameters(SocialProvider::Microsoft, ['tenant' => '...']) om maar één tenant toe te laten.Microsoft
GitHubEen GitHub OAuth-app: de client ID en client secret ervan. Zet de redirect URI in "Authorization callback URL".GitHub
FacebookEen app in Facebook for Developers: de App ID en App Secret ervan. Zet de redirect URI in de Facebook Login-instellingen van de app, in de lijst met OAuth redirect URI's.Facebook
X (Twitter)Een X developer-app: de API key en API secret ervan. Zet de redirect URI in de callback-URL van de app.Twitter
YahooEen Yahoo developer-app met de OpenID Connect-rechten profile en email: de client ID en client secret ervan. Registreer daar de redirect URI.Yahoo

3. Voeg je domeinen toe aan de geautoriseerde domeinen

Open Authentication → Settings → Authorized domains en klik op Add domain. Voeg elk domein toe dat een Firebase-panel serveert, bijvoorbeeld admin.example.com. Voeg voor lokale ontwikkeling ook localhost toe, maar alleen aan een ontwikkelproject: nieuwe projecten bevatten het standaard niet meer, en Firebase raadt het af in productie. Zonder het domein mislukt de pop-up voor social login met auth/unauthorized-domain.

Firebase-handleiding: de opmerking "Authorized domains" in Email link authentication.

4. Maak de servercredentials aan (serviceaccount)

De server communiceert met Firebase via kreait/laravel-firebase en gebruikt daarvoor een serviceaccount.

  1. Open Project settings → Service accounts.

  2. Klik op Generate new private key en daarna op Generate key. De browser downloadt een JSON-bestand.

  3. Verplaats het bestand naar je app, buiten de public-directory, bijvoorbeeld storage/app/firebase/service-account.json.

  4. Voeg het bestand toe aan .gitignore. Commit het nooit. Iedereen met dit bestand kan elke gebruiker van het project beheren.

/storage/app/firebase/service-account.json
  1. Laat kreait/laravel-firebase er in .env naar verwijzen. Een relatief pad begint bij de root van je app:

FIREBASE_CREDENTIALS=storage/app/firebase/service-account.json

kreait/laravel-firebase leest ook GOOGLE_APPLICATION_CREDENTIALS wanneer FIREBASE_CREDENTIALS leeg is. Geef in productie elke server een eigen kopie van het bestand, bijvoorbeeld via je secret store of deploytool.

Je hebt het configbestand van kreait/laravel-firebase alleen nodig als je meer wilt wijzigen dan de credentials (bijvoorbeeld de HTTP-timeout). Zo publiceer je het:

php artisan vendor:publish --provider="Kreait\Laravel\Firebase\ServiceProvider" --tag=config

Laat FIREBASE_AUTH_TENANT_ID leeg. De plugin ondersteunt geen Firebase multi-tenancy.

Firebase-handleiding: Add the Firebase Admin SDK to your server.

5. Registreer een webapp voor de social-knoppen

De social-knoppen draaien de Firebase JS SDK in de browser. Die SDK heeft de config van een Firebase-webapp nodig. Je kunt deze stap overslaan als het panel geen social providers heeft.

  1. Klik op de overzichtspagina van het project op het Web-icoon (</>). Heeft het project al apps, klik dan op Add app en daarna op Web.

  2. Vul een bijnaam in en klik op Register app. De plugin heeft Firebase Hosting niet nodig.

  3. Kopieer de waarden van het firebaseConfig-object naar .env:

firebaseConfig-sleutelEnv-sleutel van de plugin
apiKeyFIREBASE_WEB_API_KEY
authDomainFIREBASE_WEB_AUTH_DOMAIN
projectIdFIREBASE_WEB_PROJECT_ID
appIdFIREBASE_WEB_APP_ID
FIREBASE_WEB_API_KEY=AIza...
FIREBASE_WEB_AUTH_DOMAIN=your-project-id.firebaseapp.com
FIREBASE_WEB_PROJECT_ID=your-project-id
FIREBASE_WEB_APP_ID=1:1234567890:web:abc123

Dezelfde waarden vind je later onder Project settings → General → Your apps → Config.

De web-apiKey is geen geheim. Hij identificeert alleen je Firebase-project, en elke bezoeker van de loginpagina kan hem zien. Beperk hem toch: open in de Google Cloud console APIs & Services → Credentials en bewerk de sleutel. Sta onder Application restrictions alleen je paneldomeinen en je authDomain (your-project-id.firebaseapp.com) toe als HTTP-referrers. De login-pop-up draait op het authDomain en gebruikt de sleutel ook. Sta onder API restrictions alleen de API's toe die Firebase Authentication nodig heeft (Identity Toolkit API en Token Service API). Test de wijziging eerst op een testsleutel, want een foute beperking breekt het inloggen.

Op een gedeeld Firebase-project gebruiken de webapps van het project vaak dezelfde browsersleutel. Een beperking voor alleen het panel breekt dan de andere apps: hun domeinen zijn niet toegestaan, en de API's die ze nodig hebben, zijn geblokkeerd. Neem dus ofwel de domeinen en de API's van alle apps die de sleutel gebruiken op in de beperking, ofwel maak een aparte browsersleutel voor het panel (APIs & Services → Credentials → Create credentials → API key), zet die in FIREBASE_WEB_API_KEY en beperk alleen die sleutel. Lees Learn about using and managing API keys for Firebase.

6. Aanbevolen instellingen

  • Email enumeration protection: open Authentication → Settings → User actions. Firebase zet deze bescherming standaard aan voor projecten die op of na 15 september 2023 zijn aangemaakt. De plugin werkt met de bescherming aan of uit, en toont in beide gevallen dezelfde foutmelding bij elke mislukte login.

  • Password policy: open Authentication → Settings → Password policy om een minimale lengte of tekentypes te vereisen. De registratie- en profielformulieren van Filament controleren Password::defaults() van Laravel. Stel daar dezelfde regels in, zodat de formulieren ze ook controleren. Op een gedeeld project geldt het beleid voor alle apps. Laat je het beleid bij het inloggen een update van niet-conforme wachtwoorden vereisen, dan kunnen bestaande gebruikers van de andere apps geblokkeerd raken tot ze hun wachtwoord wijzigen.

  • Email templates: de plugin gebruikt de e-mailtemplates van Firebase niet. E-mails voor wachtwoordreset, e-mailverificatie en e-mailwijziging komen van Filament en Laravel. Configureer daarvoor Laravel mail (MAIL_MAILER en de rest).

Firebase-handleiding: wachtwoordbeleid en bescherming tegen e-mailenumeratie.

7. Lokale ontwikkeling met de Firebase Auth Emulator

Je kunt ontwikkelen zonder een echt Firebase-project. De Firebase Auth Emulator heeft Node en Java 21 of hoger nodig.

npm install --save-dev --save-exact firebase-tools
npx --no-install firebase-tools emulators:start --only auth --project demo-your-app

Zonder firebase.json luistert de Auth-emulator op zijn standaardpoort 9099. Om host en poort vast te leggen, voeg je een firebase.json aan je app toe, zoals de plugin dat doet:

{
  "emulators": {
    "auth": { "host": "127.0.0.1", "port": 9099 }
  }
}

Zet daarna in .env:

FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099
FIREBASE_CREDENTIALS='{"type":"service_account","project_id":"demo-your-app","client_email":"emulator@demo-your-app.iam.gserviceaccount.com","private_key":"unused"}'

kreait/firebase-php stuurt dan alle Auth-calls naar de emulator. Een project-ID dat met demo- begint, heeft geen echt Firebase-project nodig. De emulator negeert credentials. De placeholder geeft kreait/firebase-php alleen de project-ID. Zonder placeholder zoekt kreait/firebase-php naar Google-credentials op je machine: een trage probe van de cloud-metadataserver, of je echte credentials. Inloggen met e-mailadres en wachtwoord, registratie, wachtwoordreset, verificatie, de profielpagina en make:filament-user werken met de emulator. De social-knoppen niet: test die met een echt ontwikkelproject. De emulator houdt zijn accounts in het geheugen. Na een herstart zijn ze weg.

kreait/firebase-php leest FIREBASE_AUTH_EMULATOR_HOST uit de procesomgeving, niet uit de Laravel-config. Na php artisan config:cache laadt Laravel .env niet meer, dus de waarde in .env heeft geen effect. Zet de variabele dan in de echte omgeving van het proces (shell, Docker, procesmanager), of cache de config niet bij lokale ontwikkeling.

Let op: zet FIREBASE_AUTH_EMULATOR_HOST nooit in productie.Met deze variabele controleert kreait/firebase-php de handtekening van ID-tokens niet. Iedereen kan dan een vervalst token naar de route voor social login posten en als elke willekeurige gebruiker inloggen.

8. Firebase-probleemoplossing

SymptoomOorzaak en oplossing
De social-knop toont "The browser blocked the sign-in window."De browser heeft de pop-up geblokkeerd (auth/popup-blocked). Sta pop-ups toe voor je site en klik opnieuw.
De social-knop toont meteen "The sign-in failed. Try again."Vaak auth/unauthorized-domain: voeg het domein toe aan de geautoriseerde domeinen (stap 3). Of auth/operation-not-allowed: schakel de provider in onder Sign-in method (stap 2). Of de FIREBASE_WEB_*-waarden kloppen niet (stap 5).
"Unable to determine the Firebase Project ID"kreait/firebase-php heeft geen credentials gevonden. Controleer FIREBASE_CREDENTIALS en het bestandspad (stap 4). Gebruik bij de emulator de placeholder-credentials (stap 7). Voer na een wijziging van .env het commando php artisan config:clear uit.
Een fout terwijl kreait/firebase-php de credentials leestHet bestand is niet de JSON van het serviceaccount, of het is onvolledig. Genereer een nieuwe sleutel (stap 4). Gebruik niet de webconfig of een google-services.json-bestand.
Elke login met wachtwoord mislukt, en de log zegt dat Firebase faaldeHet serviceaccount is fout of verwijderd, of de server kan Firebase niet bereiken. De plugin logt alleen de exceptionklasse. Controleer stap 4 en het netwerk.
De social-knop toont "The page has expired. Reload the page and try again."Het CSRF-token van de pagina is verlopen (HTTP 419), bijvoorbeeld na lange inactiviteit. Herlaad de loginpagina.

Configureer de auth-guard, provider en password broker

Elk Firebase-panel gebruikt een eigen guard. De guard gebruikt een user provider met de firebase-driver. De password broker van het panel gebruikt ook de firebase-driver.

// config/auth.php

'guards' => [
    'admin' => [
        'driver' => 'session',
        'provider' => 'firebase_admins',
    ],
],

'providers' => [
    'firebase_admins' => [
        'driver' => 'firebase',
        'model' => App\Models\Admin::class,
    ],
],

'passwords' => [
    'admins' => [
        'driver' => 'firebase',
        'provider' => 'firebase_admins',
        'table' => 'password_reset_tokens',
        'expire' => 60,
        'throttle' => 60,
    ],
],

De firebase-broker bewaart zijn tokens in een databasetabel, dus table is verplicht. De standaardtabel password_reset_tokens van Laravel werkt.

Configureer het panel

Login, wachtwoordreset en e-mailverificatie zijn de eigen pagina's van Filament. Je zet ze aan zoals gewoonlijk. Registratie en profiel hebben de pagina's van de plugin nodig, omdat de pagina's van Filament een lokaal wachtwoord zouden opslaan. Gebruik voor multifactorauthenticatie de AppAuthentication van de plugin en de required-MFA-middleware van de plugin.

use Filament\Panel;
use Isapp\FilamentFirebaseAuth\Enums\SocialProvider;
use Isapp\FilamentFirebaseAuth\FilamentFirebaseAuthPlugin;
use Isapp\FilamentFirebaseAuth\Http\Middleware\EnsureMultiFactorAuthenticationIsEnabled;
use Isapp\FilamentFirebaseAuth\MultiFactor\AppAuthentication;
use Isapp\FilamentFirebaseAuth\Pages;

public function panel(Panel $panel): Panel
{
    return $panel
        ->id('admin')
        ->path('admin')
        ->authGuard('admin')
        ->authPasswordBroker('admins')
        ->login()
        ->registration(Pages\Register::class)
        ->passwordReset()
        ->emailVerification()
        ->emailChangeVerification()
        ->profile(Pages\EditProfile::class)
        ->multiFactorAuthentication([
            AppAuthentication::make()->recoverable(),
        ])
        ->multiFactorAuthenticationRequiredMiddlewareName(EnsureMultiFactorAuthenticationIsEnabled::class)
        // ->middleware([...]), ->authMiddleware([...]) and the rest as usual.
        ->plugin(
            FilamentFirebaseAuthPlugin::make()
                ->createOnSignIn(false)
                ->providers([SocialProvider::Google, SocialProvider::Microsoft])
                ->providerParameters(SocialProvider::Microsoft, ['tenant' => 'your-tenant-id']),
        );
}

Roep ->id() aan vóór ->plugin(). De plugin leest de panel-middleware wanneer je de plugin toevoegt.

Bij elke panelrequest controleert de plugin deze setup. De plugin controleert elk panel waarvan de guard de firebase-driver gebruikt, ook een panel zonder de plugin. De plugin gooit een LogicException wanneer zo'n panel:

  • de eigen registratiepagina, profielpagina, app-authenticatie of required-MFA-middleware van Filament gebruikt,

  • ->passwordReset() heeft, maar de password broker ervan niet de firebase-driver gebruikt (zonder ->authPasswordBroker() gebruikt Filament de standaardbroker uit config/auth.php),

  • een model heeft zonder de HasFirebaseIdentity-trait.

De melding noemt de oplossing. De plugin slaat nooit stilzwijgend een lokaal wachtwoord op.

Houd ->emailChangeVerification() aan op een panel met de profielpagina. Zonder deze instelling verandert een e-mailwijziging in het profiel het gedeelde Firebase-e-mailadres meteen, zonder bewijs dat de persoon eigenaar is van het nieuwe adres. Wachtwoordresets in alle apps op het project gaan dan naar het nieuwe adres.

Plugin-opties

MethodeStandaardWat het doet
createOnSignIn(bool | Closure)trueBij true maakt een login met een Firebase-account zonder lokaal record dat record aan ("open" panel). Bij false kunnen alleen gekoppelde of koppelbare records inloggen ("gesloten" panel).
providers(array)[]De SocialProvider-cases die een knop krijgen op de login- en registratiepagina's, in deze volgorde. Geen providers: geen knoppen en geen login-route.
providerScopes(SocialProvider, array)[]Extra OAuth-scopes voor één provider. Apple vraagt altijd om email en name.
providerParameters(SocialProvider, array)[]Eigen OAuth-parameters voor één provider, bijvoorbeeld ['tenant' => '...'] voor Microsoft of ['prompt' => 'select_account'] voor Google.

createOnSignIn() regelt de registratiepagina niet. Registratie is de eigen ->registration()-instelling van Filament.

Stel de providers in de panelconfiguratie in, niet later tijdens runtime. De plugin voegt de login-route alleen toe aan een panel dat providers heeft op het moment dat Laravel de routes laadt (ook met php artisan route:cache).

Bereid je users-tabel voor

De plugin slaat nooit een wachtwoord op. Je tabel mag de password-kolom houden, maar de kolom moet null accepteren. Anders mislukt de eerste login van een nieuwe gebruiker met een melding die naar deze sectie verwijst.

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('password')->nullable()->change();
        });
    }
};

Je kunt de kolom ook verwijderen als geen andere code hem gebruikt.

De plugin verwacht ook:

  • een name- en een email-kolom in de tabel,

  • name en email in de $fillable-lijst van het model (het standaard aanmaken van records gebruikt ze).

Firebase slaat e-mailadressen op in kleine letters. Bewaar lokale e-mailadressen ook in kleine letters op een database met hoofdlettergevoelige vergelijkingen (SQLite, PostgreSQL). Anders matcht een lokaal record met Jane@example.com niet met de Firebase-gebruiker jane@example.com.

Bereid je model voor

Voeg de HasFirebaseIdentity-trait toe aan het model van elk Firebase-panel. Het model implementeert MustVerifyEmail en CanResetPassword, en gebruikt Notifiable.

namespace App\Models;

use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Contracts\Auth\CanResetPassword;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Isapp\FilamentFirebaseAuth\Concerns\HasFirebaseIdentity;

class Admin extends Authenticatable implements CanResetPassword, FilamentUser, MustVerifyEmail
{
    use HasFirebaseIdentity;
    use Notifiable;

    protected $fillable = [
        'name',
        'email',
    ];

    public function canAccessPanel(Panel $panel): bool
    {
        return true; // Your own rule.
    }
}

De trait leest de verified-vlag uit de koppeltabel en geeft vanuit getAuthPassword() een sessie-fingerprint terug. markEmailAsVerified() en markEmailAsUnverified() schrijven de vlag naar Firebase, zodat de wijziging zichtbaar is in alle apps op het Firebase-project. markEmailAsVerified() leest eerst de Firebase-gebruiker. Is het Firebase-e-mailadres niet het lokale e-mailadres dat de verificatielink heeft bewezen, dan verandert de methode niets en geeft ze false terug. De trait verwijdert ook de koppeling wanneer het record verwijderd wordt.

Gebruikt je model de Authenticatable- of MustVerifyEmail-traits van Laravel rechtstreeks (niet via Illuminate\Foundation\Auth\User), dan meldt PHP een methodeconflict. Zeg PHP dat het de methoden van de plugin-trait moet gebruiken:

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Isapp\FilamentFirebaseAuth\Concerns\HasFirebaseIdentity;

class Admin extends Model implements AuthenticatableContract, CanResetPasswordContract, MustVerifyEmailContract
{
    use Notifiable;
    use Authenticatable, CanResetPassword, MustVerifyEmail, HasFirebaseIdentity {
        HasFirebaseIdentity::getAuthPassword insteadof Authenticatable;
        HasFirebaseIdentity::hasVerifiedEmail insteadof MustVerifyEmail;
        HasFirebaseIdentity::markEmailAsVerified insteadof MustVerifyEmail;
        HasFirebaseIdentity::markEmailAsUnverified insteadof MustVerifyEmail;
        HasFirebaseIdentity::sendEmailVerificationNotification insteadof MustVerifyEmail;
        HasFirebaseIdentity::getEmailForVerification insteadof MustVerifyEmail;
    }
}

Twee panels kunnen één model delen. Dan delen ze één record per Firebase-gebruiker. Twee panels met verschillende modellen krijgen voor dezelfde Firebase-gebruiker één record in elk model.

Hoe een login het lokale record vindt

Bij elke login (wachtwoord of social) past de plugin deze regels in volgorde toe:

  1. Er bestaat een koppeling voor deze Firebase-UID en dit model: gebruik het record van die koppeling.

  2. Geen koppeling, maar een record heeft hetzelfde e-mailadres: koppel dat record, maar alleen als Firebase het e-mailadres heeft geverifieerd. Iedereen kan een niet-geverifieerd Firebase-account aanmaken met het e-mailadres van iemand anders. Daarom koppelt een niet-geverifieerd e-mailadres nooit een bestaand record.

  3. Geen koppeling, geen record, en het panel is open (createOnSignIn(true)): maak het record aan.

  4. Anders mislukt de login.

De login mislukt ook voor een uitgeschakeld Firebase-account, voor een record dat aan een andere Firebase-UID gekoppeld is en voor een gebruiker zonder toegang tot het panel. Een Firebase-gebruiker zonder e-mailadres (bijvoorbeeld een Twitter-account zonder de e-mailtoestemming) kan ook niet inloggen, tenzij er al een koppeling bestaat. Elke mislukking toont de eigen melding "These credentials do not match our records" van Filament, zodat niemand kan achterhalen welke e-mailadressen bestaan.

Regel 2 vertrouwt de emailVerified-vlag van Firebase, ongeacht welke provider die vlag heeft gezet. Laat alleen providers waarvan je het e-mailadres vertrouwt bestaande records via e-mailadres koppelen.

De firebase-driver past extra credentials en withQuery() toe zoals de eloquent-driver van Laravel dat doet. Als je bijvoorbeeld 'is_active' => true toevoegt aan de credentials van de loginpagina, kan een record met is_active = false niet inloggen. Deze voorwaarden beperken een bestaand record. Een nieuw record op een open panel hoeft er niet aan te voldoen.

Social login

Voeg providers toe met de SocialProvider-enum: Google, Apple, Microsoft, GitHub, Facebook, Twitter en Yahoo. De knoppen verschijnen via render hooks onder de eigen login- en registratieformulieren van Filament. Het JavaScript laadt alleen op deze pagina's.

De browser logt in met een Firebase-pop-up en post het Firebase ID-token naar een route van het panel. De server controleert het token met verifyIdToken(), ook op intrekking. Het token moet:

  • afkomstig zijn van een van de providers van dit panel (een wachtwoordtoken, anoniem token of custom token mislukt),

  • een login van de laatste 5 minuten bewijzen.

De route heeft de panel-middleware, de CSRF-controle en een rate limit van 5 pogingen per minuut per panel en IP-adres.

Social login vertrouwt de provider en de eigen tweede factor van die provider. Social login toont de multifactor-challenge van Filament niet. Heb je een tweede factor nodig voor deze gebruikers, zet die dan aan bij de provider (bijvoorbeeld in Google Workspace of Microsoft Entra). dutchcodingcompany/filament-socialite volgt dezelfde aanpak. Lees de waarschuwing in Tweefactorauthenticatie: deze regel geldt ook voor gebruikers met een wachtwoord.

Tweefactorauthenticatie

De multifactorauthenticatie van Filament beschermt inloggen met e-mailadres en wachtwoord. De loginpagina van Filament voert de challenge zoals gewoonlijk uit.

  • Gebruik Isapp\FilamentFirebaseAuth\MultiFactor\AppAuthentication in plaats van de AppAuthentication van Filament. De dialoogvensters van deze klasse voor instellen, uitschakelen en herstelcodes controleren het wachtwoord bij Firebase. De eigen klasse van Filament vergelijkt een lokale hash, en die is er niet.

  • Bereid het model voor op app-authenticatie zoals de "Multi-factor authentication"-docs van Filament beschrijven: implementeer HasAppAuthentication (en HasAppAuthenticationRecovery voor herstelcodes), gebruik de bijbehorende InteractsWith...-traits en voeg de kolommen app_authentication_secret (en app_authentication_recovery_codes) toe.

  • De e-mailcode-authenticatie van Filament heeft geen wachtwoordstap. Ze werkt zonder pluginklasse.

  • Is multifactorauthenticatie verplicht, stel dan de plugin-middleware in met ->multiFactorAuthenticationRequiredMiddlewareName(EnsureMultiFactorAuthenticationIsEnabled::class). Een gebruiker die alleen met een social provider inlogt, komt er dan door. Alle andere gebruikers krijgen het gedrag van Filament.

Waarschuwing: social login slaat de multifactor-challenge van Filament over voor elke gebruiker, ook voor een gebruiker met een Firebase-wachtwoord.Iemand die het Firebase-wachtwoord van een account kent, kan rechtstreeks bij Firebase inloggen (bijvoorbeeld via de Firebase REST API), een eigen Google- of GitHub-account aan die Firebase-gebruiker koppelen en daarna de social-knop gebruiken. De plugin logt die persoon dan in zonder multifactor-challenge van Filament. De multifactorauthenticatie van Filament beschermt alleen het formulier voor e-mailadres en wachtwoord. Zo beperk je dit risico:

  • Voeg geen social providers (->providers([...])) toe aan een panel dat multifactorauthenticatie vereist. Dit is de enige volledige oplossing.

  • Houd de Firebase-standaard van één account per e-mailadres aan (Authentication → Settings → User account linking). Die standaard voorkomt een tweede Firebase-account met hetzelfde e-mailadres. De koppelstap hierboven voorkomt die standaard niet.

  • Laat email enumeration protection aanstaan, zodat een aanvaller de e-mailadressen van je accounts niet makkelijk kan vinden.

  • Vereis een tweede factor bij de provider voor de accounts die ertoe doen (bijvoorbeeld in Google Workspace of Microsoft Entra).

Gebruikers zonder Firebase-wachtwoord zien geen acties voor app-authenticatie op de profielpagina. De sectiekop kan nog steeds verschijnen, zonder inhoud.

Stel de plugin-middleware in telkens wanneer het panel multifactorauthenticatie gebruikt. De setupcontrole leest isRequired één keer, wanneer het panel opstart. Ze kan geen closure zien die van de gebruiker afhangt.

Voor een gebruiker die alleen met een social provider inlogt, op een panel dat multifactorauthenticatie vereist, vraagt de middleware Firebase één keer per request naar de providers van de gebruiker.

Sessies

Sessiecontrole

Een andere app of de Firebase console kan een account verwijderen of uitschakelen, of de tokens ervan intrekken (Firebase doet dat bij een wachtwoordwijziging). Firebase brengt je app daarvan niet op de hoogte. Daarom controleert de plugin de Firebase-gebruiker van elke sessie op de achtergrond:

  • Na revalidate_every minuten (standaard 15) start de volgende panelrequest een controle.

  • Met revalidate_queue op null draait de controle na de response in hetzelfde proces. De request wacht niet op Firebase.

  • Stel een queuenaam in om de controle op een queue worker te draaien. De job gebruikt de standaard queue connection (QUEUE_CONNECTION). Op de sync-connection draait de job meteen, en die request wacht op Firebase.

  • Wanneer het account ontbreekt of uitgeschakeld is, of de tokens ervan ingetrokken zijn, verandert de sessie-fingerprint.

  • De fingerprint verandert ook wanneer het e-mailadres geverifieerd wordt, of wanneer de Firebase-gebruiker de password-provider verliest (bijvoorbeeld nadat de eigenaar van het e-mailadres met Google inlogt en Firebase een onbewezen wachtwoord verwijdert). Dan verliest iemand die het account met het e-mailadres van iemand anders heeft aangemaakt de sessie.

  • De AuthenticateSession-middleware van Laravel logt de sessie dan uit bij de volgende request, ook een "remember me"-sessie.

  • Als Firebase onbereikbaar is, verandert er niets. Bij het volgende interval probeert de plugin het opnieuw.

De plugin voegt de AuthenticateSession-middleware van Filament aan het panel toe als het panel die nog niet heeft. Zet revalidate_every op 0 om de controle uit te zetten.

Andere sessies uitloggen

De profielpagina heeft een actie "Log out other sessions". Die actie logt alle andere sessies van dit record in deze app uit. De huidige sessie blijft ingelogd.

Overal uitloggen

Zet global_logout op true om een actie "Log out everywhere" op de profielpagina te tonen. Die actie trekt de Firebase-refreshtokens van de gebruiker in. Dit logt de persoon ook uit in je andere apps op hetzelfde Firebase-project. Daarom staat de actie standaard uit.

Een gebruiker die alleen met een social provider inlogt, heeft geen Firebase-wachtwoord. Voor deze gebruiker vragen "Delete account", "Log out other sessions" en "Log out everywhere" geen wachtwoord, alleen een bevestiging. Iedereen met een gestolen sessie van zo'n gebruiker kan ze dus gebruiken. Met delete_accounts = orphaned verwijdert één klik dan het gedeelde Firebase-account.

Na een wachtwoordwijziging, een e-mailwijziging, "log out other sessions" en "log out everywhere" logt de plugin de huidige sessie opnieuw in met de nieuwe fingerprint. Laravel vuurt hiervoor zijn Login-event af.

Accountverwijdering

De plugin verwijdert altijd de lokale koppeling wanneer een record verwijderd wordt. De configsleutel delete_accounts bepaalt wat er met het Firebase-account gebeurt:

delete_accountsWat de plugin in Firebase verwijdert
never (standaard)Niets. Alleen lokale data.
new_onlyAlleen een account dat Firebase tijdens deze login heeft aangemaakt, wanneer een gesloten panel (createOnSignIn(false)) de login blokkeert. Bijvoorbeeld: een onbekende logt met Google in op een gesloten panel.
orphanedElk account zonder resterende koppeling in deze app: na een blokkering op een gesloten panel, nadat de gebruiker het account op de profielpagina heeft verwijderd, of nadat code of een beheerder het record heeft verwijderd.

Waarschuwing: gebruik orphaned niet wanneer andere apps het Firebase-project delen.De plugin ziet alleen zijn eigen koppelingen. De plugin kan niet weten dat een mobiele app of een andere webapp het account nog gebruikt. Met orphaned verwijdert het verwijderen van een record in deze app het Firebase-account voor elke app, en verliest de persoon overal toegang. Ook een geblokkeerde login op een gesloten panel leidt tot verwijdering: wanneer iemand zonder koppeling in deze app inlogt met het juiste wachtwoord of met een social provider zoals Google (bijvoorbeeld een gebruiker van je mobiele app), verwijdert orphaned het Firebase-account van die persoon voor alle apps. Gebruik orphaned alleen wanneer deze app de enige gebruiker van het Firebase-project is. Houd in alle andere gevallen never aan.

Meer details:

  • new_only vergelijkt het aanmaaktijdstip en het tijdstip van de laatste login van het Firebase-account. Beide moeten binnen new_account_window seconden (standaard 60) van elkaar en van nu liggen.

  • Het Firebase-deel draait nadat de databasetransactie is gecommit. Een Firebase-fout stopt de lokale verwijdering nooit. De plugin logt de UID en de exceptionklasse.

  • Een soft delete houdt de koppeling. Een force delete verwijdert de koppeling.

  • Bulkverwijderingen via de query builder (Admin::query()->where(...)->delete()) slaan model-events over. Ze verwijderen noch de koppeling, noch het Firebase-account. De koppeling die blijft, blokkeert die Firebase-UID in dit model. Verwijder records één voor één, of verwijder de koppelrijen zelf.

  • Wanneer de registratie mislukt nadat de plugin een Firebase-account heeft aangemaakt, verwijdert de plugin dat account in elke modus, omdat de plugin het in dezelfde request heeft aangemaakt.

Gebruikers aanmaken met make:filament-user

De plugin vervangt het make:filament-user-commando van Filament (en de aliassen ervan). De opties blijven hetzelfde:

php artisan make:filament-user --panel=admin --name="Jane Doe" --email=jane@example.com

Het commando vraagt om het wachtwoord. Geef --password niet mee op de command line: de shellgeschiedenis bewaart het.

Op een panel waarvan de guard de firebase-driver gebruikt:

  • Geen Firebase-account heeft dit e-mailadres: het commando maakt er een aan met een geverifieerd e-mailadres, daarna het lokale record en de koppeling.

  • Er bestaat een Firebase-account met dit e-mailadres: het commando behoudt het wachtwoord ervan en koppelt het account. Het commando weigert een uitgeschakeld account, een niet-geverifieerd e-mailadres en een account dat een ander record van het panelmodel al gebruikt.

  • Wanneer de lokale stap mislukt, verwijdert het commando het Firebase-account alleen als het commando dat account zelf heeft aangemaakt.

Op elk ander panel voert het commando het normale gedrag van Filament uit.

Uitbreidingspunten

Bind je eigen klasse in de register()-methode van een service provider. De plugin bindt zijn standaardklassen alleen wanneer er nog niets anders gebonden is.

ContractStandaardWanneer het draait
Isapp\FilamentFirebaseAuth\Contracts\CreatesLocalUsersActions\LocalUsers\CreateLocalUserEen login, registratie of make:filament-user heeft een nieuw lokaal record nodig.
Isapp\FilamentFirebaseAuth\Contracts\SyncsLocalUsersActions\LocalUsers\SyncLocalUserBij elke login en elke sessiecontrole. Kopieert de naam en het e-mailadres uit Firebase.
Isapp\FilamentFirebaseAuth\Contracts\DecidesAccountDeletionDeletion\AccountDeletionPolicyDe plugin kan een Firebase-account verwijderen (zie Accountverwijdering).

Voorbeeld: geef elke nieuwe admin een standaardrol.

namespace App\Actions;

use Filament\Panel;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Isapp\FilamentFirebaseAuth\Contracts\CreatesLocalUsers;
use Kreait\Firebase\Auth\UserRecord;

class CreateAdmin implements CreatesLocalUsers
{
    public function handle(UserRecord $firebaseUser, string $model, ?Panel $panel): Model & Authenticatable
    {
        return $model::query()->create([
            'name' => $firebaseUser->displayName ?? $firebaseUser->email,
            'email' => $firebaseUser->email,
            'role' => 'viewer',
        ]);
    }
}

create() vult alleen de attributen in de $fillable-lijst van het model. Voeg role daar dus toe, of gebruik forceCreate().

// app/Providers/AppServiceProvider.php

use App\Actions\CreateAdmin;
use Isapp\FilamentFirebaseAuth\Contracts\CreatesLocalUsers;

public function register(): void
{
    $this->app->bind(CreatesLocalUsers::class, CreateAdmin::class);
}

De klasse krijgt alleen de Firebase-gebruiker. Extra velden van het registratieformulier bereiken de klasse niet. Ook de output van mutateFormDataBeforeRegister() bereikt het record niet: de pluginpagina bouwt het record op uit de Firebase-gebruiker. Om extra formuliervelden of gemuteerde data op te slaan, maak je een subklasse van de Pages\Register-pagina van de plugin en gebruik je de afterRegister()-hook van Filament.

Een eigen SyncsLocalUsers moet ook de koppelkolommen bijwerken, anders werkt de sessiecontrole niet meer. De eenvoudigste manier is de standaardklasse uit te breiden:

namespace App\Actions;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Isapp\FilamentFirebaseAuth\Actions\LocalUsers\SyncLocalUser;
use Kreait\Firebase\Auth\UserRecord;

class SyncAdmin extends SyncLocalUser
{
    public function handle(Model & Authenticatable $user, UserRecord $firebaseUser): void
    {
        parent::handle($user, $firebaseUser);

        $user->forceFill(['avatar_url' => $firebaseUser->photoUrl])->save();
    }
}

Schrijf je de klasse helemaal zelf, injecteer dan Isapp\FilamentFirebaseAuth\Actions\Identity\RefreshFirebaseIdentity en roep de handle()-methode ervan aan met de koppeling van het record.

Een eigen DecidesAccountDeletion draait alleen wanneer delete_accounts niet never is. In de modus never raadpleegt de plugin deze klasse nooit.

Al het andere gedrag zit in action-klassen met één doel in Isapp\FilamentFirebaseAuth\Actions\..., elk met één handle()-methode. Je kunt ze in je eigen code injecteren, bijvoorbeeld Actions\Firebase\FindOrCreateFirebaseUser en Actions\Identity\LinkFirebaseIdentity om gebruikers aan te maken vanuit een Filament-resource.

Configuratie

config/filament-firebase-auth.php:

SleutelStandaardBetekenis
tablefirebase_identitiesDe koppeltabel.
delete_accountsnevernever, new_only of orphaned. Lees Accountverwijdering.
new_account_window60Seconden. Voor new_only telt een account dat jonger is dan deze waarde als nieuw.
revalidate_every15Minuten tussen sessiecontroles. 0 zet de controle uit.
revalidate_queuenullQueue van de job voor de sessiecontrole, op de standaard queue connection. null draait de job na de response.
global_logoutfalseToont "Log out everywhere" op de profielpagina.
social_sign_injs_sdkHoe social login werkt. js_sdk is de enige waarde.
web.api_key, web.auth_domain, web.project_id, web.app_idenvDe Firebase-webconfig voor de social-knoppen.

De servercredentials blijven in de config van kreait/laravel-firebase (config/firebase.php).

Probleemoplossing

"If you cannot sign in, reset your password."De plugin koppelt een bestaand lokaal record alleen aan een Firebase-account wanneer Firebase het e-mailadres heeft geverifieerd. Een wachtwoordreset bewijst dat de persoon eigenaar is van het e-mailadres. Daarom markeert de reset het e-mailadres als geverifieerd. De volgende login koppelt dan het record.

De pop-up voor social login is geblokkeerd of sluit meteen.Lees Firebase-probleemoplossing.

"This email already has an account with another sign-in method."Standaard staat Firebase één account per e-mailadres toe. De persoon heeft het account met een andere methode aangemaakt, bijvoorbeeld met een wachtwoord. Log in met die methode. De plugin koppelt geen providers aan een bestaand Firebase-account.

De eerste login van een nieuwe gebruiker mislukt, en de log noemt een NOT NULL-kolom password.Lees Bereid je users-tabel voor.

Een LogicException noemt de registratiepagina, profielpagina, app-authenticatie of middleware van Filament.Gebruik de pluginklasse die de melding noemt. Lees Configureer het panel.

Een link voor wachtwoordreset mislukt.De reset zoekt het record op e-mailadres. Wanneer een andere app het Firebase-e-mailadres heeft gewijzigd, volgt het lokale e-mailadres bij de volgende login of sessiecontrole. Tot dan kan de reset mislukken. Gebruikers die alleen met een social provider inloggen, krijgen geen reset-e-mail, omdat ze geen Firebase-wachtwoord hebben.

Inloggen mislukt, en de log zegt dat Firebase faalde.De plugin toont de normale foutmelding wanneer Firebase onbereikbaar is. De plugin logt alleen de exceptionklasse, nooit het wachtwoord of het token. Controleer het serviceaccount en het netwerk.

Je app testen met de Firebase Auth Emulator

kreait/firebase-php stuurt zijn Auth-calls naar de Firebase Auth Emulator wanneer FIREBASE_AUTH_EMULATOR_HOST gezet is. Alle server-side Firebase-calls van de plugin gaan via die library, dus je feature tests hebben geen echt Firebase-project nodig.

De emulator heeft Node en Java 21 of hoger nodig. Pin firebase-tools als dev dependency van je app, zodat elke machine dezelfde versie draait:

npm install --save-dev --save-exact firebase-tools
npm ci
npx --no-install firebase-tools emulators:exec --project demo-your-app --only auth "php artisan test"

emulators:exec start de emulator, zet FIREBASE_AUTH_EMULATOR_HOST voor het commando en stopt de emulator daarna. Een project-ID dat met demo- begint, heeft geen echt Firebase-project nodig.

De emulator negeert credentials, maar kreait/firebase-php heeft nog steeds een project-ID nodig. Zonder credentials zoekt de library naar Application Default Credentials: een trage probe van de cloud-metadataserver, of de echte credentials van je machine. Geef de library in de testomgeving placeholder-credentials voor een serviceaccount, zoals de eigen emulatortests van de plugin doen. De private key wordt nooit gebruikt.

// For example in the setUp() of your emulator test case
config()->set('firebase.projects.app.credentials', [
    'type' => 'service_account',
    'project_id' => 'demo-your-app',
    'client_email' => 'emulator@demo-your-app.iam.gserviceaccount.com',
    'private_key' => 'unused',
]);

De social-knoppen in de browser maken geen verbinding met de emulator.

De unit- en feature tests van de plugin mocken Firebase. Een emulator-testgroep draait tegen de Firebase Auth Emulator (composer test:emulator). Lees CONTRIBUTING.md om ze te draaien.

Beperkingen

  • Geen passkeys. Firebase Authentication heeft nog geen login met passkeys.

  • Geen Firebase-multifactorauthenticatie. De eigen multifactorauthenticatie van Filament werkt wel. Een account met een tweede factor in Firebase (SMS of TOTP, ingesteld in een andere app) kan niet met een wachtwoord inloggen: de plugin weigert de wachtwoordcontrole en logt de code MFA_REQUIRED.

  • Geen eigen oidc.*- of saml.*-providers. Alleen de providers in de SocialProvider-enum.

  • Geen API- of token-auth. De plugin is alleen voor panelsessies.

  • Geen Firebase multi-tenancy, inloggen via telefoon of anoniem inloggen.

  • Alleen Filament 5.

  • E-mails komen van Laravel. E-mails voor wachtwoordreset en verificatie gebruiken de notificaties van Filament, niet de e-mailtemplates van Firebase.

  • Registratie kan verraden waar een e-mailadres bestaat. De plugin toont dezelfde fout "email taken" voor een lokaal record en voor een account dat alleen in Firebase bestaat. De fout kan toch bij een andere stap van de validatie verschijnen. Een oplettende bezoeker kan de twee gevallen dus mogelijk van elkaar onderscheiden.

  • Geen import van lokale wachtwoordhashes in Firebase.

Beveiliging

Vind je een beveiligingsprobleem, gebruik dan Report a vulnerability op het Security-tabblad, of stuur een e-mail naar contact@isapp.be. Open geen publiek issue. Lees het beveiligingsbeleid.

Bijdragen

Lees CONTRIBUTING.md. Wijzigingen staan in CHANGELOG.md.

Credits

Licentie

The MIT License (MIT). Lees LICENSE.md.