
![PocketID](/storage/pocketid-filament.png)

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](https://github.com/stonith404/pocket-id) 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](https://github.com/brufdev/socialiteproviders-pocketid) — a Socialite driver for PocketID's OIDC endpoints
- [dutchcodingcompany/filament-socialite](https://github.com/dutchcodingcompany/filament-socialite) — wires Socialite providers into Filament's login flow

```bash
composer require brufdev/socialiteproviders-pocketid dutchcodingcompany/filament-socialite
```

## Configuration

**config/services.php** — add PocketID credentials:

```php
'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:

```php
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:

```php
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:**

```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:

```php
->login(Login::class)
```

Now anyone hitting /dashboard unauthenticated is sent straight to PocketID, without ever seeing a username field.

## Environment Variables

```dotenv
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

1. User navigates to /dashboard — Filament detects unauthenticated, renders the Login page
2. Login::mount() fires immediately, redirecting to APP_URL/oauth/pocketid
3. filament-socialite adds a state parameter (encrypted panel ID) and hands off to PocketID
4. User authenticates on PocketID's own UI
5. PocketID redirects back to /oauth/callback/pocketid with an authorization code
6. filament-socialite exchanges the code for a token, fetches the user from PocketID's /api/oidc/userinfo
7. A SocialiteUser record is created (or found) and linked to a local User — no password stored
8. 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.
