Installation
composer require rizalrepo/sso-client
Publish Configuration
php artisan vendor:publish --tag=sso-config
config/sso.php with default values.Configure SSO
Edit config/sso.php with your OAuth provider details:
return [
'callbackUrl' => env('SSO_CALLBACK_URL', 'http://127.0.0.1:8000/callback'),
'serverUrl' => env('SSO_SERVER_URL', 'http://127.0.0.1:8081'),
'clientId' => env('SSO_CLIENT_ID', 'f9c2bbad-c06d-4028-9786-213c9113ddbb'),
'clientSecret'=> env('SSO_CLIENT_SECRET', '1zJyzTcLmL05ZzMOnaMI6DfhaY9guJLCKBisH4YS'),
];
Add Routes
Include in routes/web.php:
Route::controller(\Rizalrepo\SsoClient\Http\Controllers\SSOController::class)->group(function () {
Route::get('/login', 'getLogin')->name('sso.login');
Route::get('/callback', 'getCallback')->name('sso.callback');
});
First Use Case Redirect users to SSO login:
return redirect()->route('sso.login');
Authentication Flow
getLogin() (redirects to SSO provider).getCallback() (exchanges auth code for token).User Sync
connectUser() to map SSO claims to your User model:
public function connectUser() {
$userData = $this->ssoService->getUserData();
$user = User::updateOrCreate(
['email' => $userData['email']],
[
'name' => $userData['name'],
'avatar' => $userData['avatar'] ?? null,
]
);
auth()->login($user);
}
Middleware Integration
auth middleware:
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', 'DashboardController@index');
});
Role-Based Redirects
portal() method to redirect based on user roles:
public function portal() {
$user = auth()->user();
if ($user->isAdmin()) {
return redirect()->route('admin.dashboard');
}
return redirect()->route('user.dashboard');
}
Token Refresh Implement a job to refresh expired tokens:
$this->ssoService->refreshToken();
Custom Claims Mapping
Override getUserData() in a service class:
public function getUserData() {
$data = parent::getUserData();
return [
'email' => $data['email'],
'custom_field' => $data['custom_claim'] ?? null,
];
}
Logout Handling
Clear session and invalidate tokens in logout():
public function logout() {
$this->ssoService->logout();
auth()->logout();
return redirect('/');
}
Callback URL Mismatch
callbackUrl in sso.php matches the route exactly (including trailing slashes).env() and validate in getCallback():
if (url()->current() !== config('sso.callbackUrl')) {
abort(403, 'Invalid callback URL');
}
State Parameter Validation
state parameter in getCallback() to prevent CSRF:
if (!hash_equals(session('sso_state'), $request->state)) {
abort(403);
}
Token Storage
Cache::put('sso_token_' . auth()->id(), $token, now()->addHours(1));
Role-Based Logic
$user->getRoles()->rememberFor(1440); // Cache for 1 day
Enable Logging
Add to sso.php:
'debug' => env('SSO_DEBUG', false),
Logs OAuth responses to storage/logs/sso.log.
Test Locally
Use ngrok to expose callbackUrl for testing:
ngrok http 8000
Update callbackUrl to https://your-ngrok-url.ngrok.io/callback.
Custom Providers
Extend Rizalrepo\SsoClient\Services\SsoService for non-OAuth2 providers:
class CustomSsoService extends SsoService {
public function getUserData() {
// Custom logic for SAML, LDAP, etc.
}
}
Webhook Handling Add a route for SSO provider webhooks (e.g., user updates):
Route::post('/sso/webhook', [SsoWebhookController::class, 'handle']);
Multi-Tenancy Scope tokens to tenants:
$token = $this->ssoService->getToken(['tenant_id' => $tenant->id]);
Rate Limiting Throttle login attempts:
Route::middleware(['throttle:5,1'])->group(function () {
Route::get('/login', 'getLogin');
});
How can I help you explore Laravel packages today?