
Out of the box, Filament ships with a classic email/password login form. For a personal admin panel or a small team tool that's often fine — but it means storing password hashes in your database and building a full credential-management flow. And if you reach for Google or GitHub OAuth instead, you're trading one dependency for another.
I wanted something different: a single sign-on provider I control, running on my own infrastructure. That's where PocketID comes in.
What is PocketID?
PocketID is a lightweight, self-hosted OIDC provider. A single Docker container, a domain, and a client registration is all it takes. No user database gymnastics — it handles authentication and hands back a standard OIDC token. Your Laravel app trusts that token and creates (or finds) a local user record. Done.
Packages
Two packages do the heavy lifting:
- brufdev/socialiteproviders-pocketid — a Socialite driver for PocketID's OIDC endpoints
- dutchcodingcompany/filament-socialite — wires Socialite providers into Filament's login flow
composer require brufdev/socialiteproviders-pocketid dutchcodingcompany/filament-socialite
Configuration
config/services.php — add PocketID credentials:
'pocketid' => [
'client_id' => env('POCKETID_CLIENT_ID'),
'client_secret' => env('POCKETID_CLIENT_SECRET'),
'redirect' => env('POCKETID_REDIRECT_URI'),
'base_url' => env('POCKETID_URI'),
],
app/Providers/AppServiceProvider.php — register the Socialite driver via an event listener:
use Illuminate\Support\Facades\Event;
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
$event->extendSocialite('pocketid', \brufdev\SocialiteProvidersPocketID\Provider::class);
});
app/Providers/DashboardPanelProvider.php — add the plugin to your panel:
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use DutchCodingCompany\FilamentSocialite\Provider;
FilamentSocialitePlugin::make()
->providers([
Provider::make('pocketid')
->label(__('Sign in'))
->outlined(false)
->stateless(false),
])
->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {
return redirect('/dashboard');
})
->showDivider(false)
->registration(true),
registration(true) means the first OAuth login auto-creates a local user — no manual user provisioning needed.
Skipping the Login Form Entirely
With standard Filament Socialite, the login page shows a form with an OAuth button below. For a panel that only supports PocketID, that form is noise. Override the login page to redirect immediately:
app/Filament/Auth/Login.php:
namespace App\Filament\Auth;
use Filament\Auth\Pages\Login as BaseAuth;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\EmbeddedSchema;
use Filament\Schemas\Components\Form;
class Login extends BaseAuth
{
public function mount(): void
{
redirect(config('app.url') . '/oauth/pocketid');
}
public function getFormContentComponent(): Component
{
return Form::make([EmbeddedSchema::make('form')])
->id('form')
->visible(fn (): bool => false);
}
protected function getFormActions(): array
{
return [];
}
}
Register it in your panel provider:
->login(Login::class)
Now anyone hitting /dashboard unauthenticated is sent straight to PocketID, without ever seeing a username field.
Environment Variables
POCKETID_CLIENT_ID=your-client-id
POCKETID_CLIENT_SECRET=your-client-secret
POCKETID_REDIRECT_URI=https://your-app.com/oauth/callback/pocketid
POCKETID_URI=https://auth.your-domain.com
How It Works
- User navigates to /dashboard — Filament detects unauthenticated, renders the Login page
- Login::mount() fires immediately, redirecting to APP_URL/oauth/pocketid
- filament-socialite adds a state parameter (encrypted panel ID) and hands off to PocketID
- User authenticates on PocketID's own UI
- PocketID redirects back to /oauth/callback/pocketid with an authorization code
- filament-socialite exchanges the code for a token, fetches the user from PocketID's /api/oidc/userinfo
- A SocialiteUser record is created (or found) and linked to a local User — no password stored
- User lands on /dashboard
The result: zero password hashes in your database, no reliance on external OAuth providers, and a login UX that's a single redirect.