sfsso-frappe

Laravel integration

Laravel has a dedicated Composer package (sso-frappe-laravel) built on Laravel Socialite. PHP cannot import an npm package, so the Laravel provider is distributed via Composer/Packagist.

Requirements

  • PHP ^8.2
  • Laravel ^11.0|^12.0
  • Laravel Socialite ^5.0

Install

composer require sso-frappe/laravel

Configuration

php artisan vendor:publish --tag=sso-frappe-config
FRAPPE_SSO_BASE_URL=https://erp.example.com
FRAPPE_SSO_CLIENT_ID=<oauth-client-id>
FRAPPE_SSO_CLIENT_SECRET=<oauth-client-secret>
FRAPPE_SSO_REDIRECT_URI=https://app.example.com/auth/frappe/callback

Routes

// routes/web.php
use Laravel\Socialite\Facades\Socialite;

Route::get('/auth/frappe', fn () =>
    Socialite::driver('frappe')->redirect()
)->name('frappe.login');

Route::get('/auth/frappe/callback', function () {
    $frappeUser = Socialite::driver('frappe')->user();

    // Match/create local user (see recommended identity schema below)
    $identity = \App\Models\UserIdentity::query()
        ->where('provider', 'frappe')
        ->where('provider_subject', $frappeUser->getId())
        ->first();

    // ... link by email, or create users + user_identities, then Auth::login()
})->name('frappe.callback');

Controller example

// app/Http/Controllers/FrappeLoginController.php
namespace App\Http\Controllers;

use App\Models\User;
use App\Models\UserIdentity;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Laravel\Socialite\Facades\Socialite;

class FrappeLoginController extends Controller
{
    public function __invoke()
    {
        $frappe = Socialite::driver('frappe')->user();

        $user = DB::transaction(function () use ($frappe) {
            // Subject first
            $identity = UserIdentity::query()
                ->where('provider', 'frappe')
                ->where('provider_subject', $frappe->getId())
                ->first();

            if ($identity) {
                return $identity->user;
            }

            // Email fallback — link existing manual user
            $user = User::query()->firstOrCreate(
                ['email' => strtolower($frappe->getEmail())],
                [
                    'name' => $frappe->getName(),
                    'password' => null,
                    'status' => 'AKTIF',
                ],
            );

            $user->identities()->create([
                'provider' => 'frappe',
                'provider_subject' => $frappe->getId(),
                'provider_email' => strtolower($frappe->getEmail()),
                'metadata' => [
                    'roles' => $frappe->frappe_roles ?? [],
                ],
            ]);

            return $user;
        });

        abort_unless($user->status === 'AKTIF', 403);

        Auth::login($user, remember: true);
        request()->session()->regenerate();

        return redirect()->intended('/dashboard');
    }
}

Profile fields

Property Source Description
getId() sub Stable subject ID — the identity key
getEmail() email Email
getName() name Full name
getNickname() username Frappe username
getAvatar() user_image Avatar URL
frappe_roles roles Role array
getRaw() Raw userinfo array

Handled automatically

  • client_secret_post — Socialite sends credentials in POST body (matches Frappe)
  • PKCE S256 (on by default, disable via use_pkce => false)
  • Explicit endpoints — no OIDC discovery dependency
  • Profile normalization

Schema

See database-schema.md — the recommended identity schema applies to both JS and Laravel.