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/laravelConfiguration
php artisan vendor:publish --tag=sso-frappe-configFRAPPE_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/callbackRoutes
// 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 |
|
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.