Implements Single Sign-On with Authentik alongside the existing password login. Admins configure Client ID, Client Secret and Base URL directly in the app; the SSO button on the login page only appears when the configuration is complete and enabled. - SsoSetting model + migration (one-row config table) - sso_provider_id / is_sso_user fields on users (migration) - SsoController: redirect to Authentik + callback (token exchange, userinfo fetch, auto-create unknown users) - Admin\SsoSettingController + admin/sso/show view with setup guide and one-click Redirect URI copy - Admin dropdown: SSO-Konfiguration entry - Login page: Authentik button rendered conditionally No additional Composer packages required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.6 KiB
PHP
110 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Auth;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\SsoSetting;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Str;
|
|
|
|
class SsoController extends Controller
|
|
{
|
|
public function redirect(Request $request)
|
|
{
|
|
$sso = SsoSetting::get();
|
|
|
|
if (!$sso->isConfigured()) {
|
|
return redirect()->route('login')->with('error', 'SSO ist nicht konfiguriert.');
|
|
}
|
|
|
|
$state = Str::random(40);
|
|
session(['sso_state' => $state]);
|
|
|
|
$query = http_build_query([
|
|
'client_id' => $sso->client_id,
|
|
'redirect_uri' => route('sso.callback'),
|
|
'response_type' => 'code',
|
|
'scope' => 'openid email profile',
|
|
'state' => $state,
|
|
]);
|
|
|
|
return redirect($sso->authorizeUrl() . '?' . $query);
|
|
}
|
|
|
|
public function callback(Request $request)
|
|
{
|
|
$sso = SsoSetting::get();
|
|
|
|
if (!$sso->isConfigured()) {
|
|
return redirect()->route('login')->with('error', 'SSO ist nicht konfiguriert.');
|
|
}
|
|
|
|
// State prüfen
|
|
if ($request->get('state') !== session('sso_state')) {
|
|
return redirect()->route('login')->with('error', 'Ungültige SSO-Anfrage (state mismatch).');
|
|
}
|
|
session()->forget('sso_state');
|
|
|
|
if ($request->has('error')) {
|
|
return redirect()->route('login')->with('error', 'SSO-Anmeldung abgebrochen: ' . $request->get('error_description', $request->get('error')));
|
|
}
|
|
|
|
// Code gegen Token tauschen
|
|
$tokenResponse = Http::asForm()->post($sso->tokenUrl(), [
|
|
'grant_type' => 'authorization_code',
|
|
'client_id' => $sso->client_id,
|
|
'client_secret' => $sso->client_secret,
|
|
'redirect_uri' => route('sso.callback'),
|
|
'code' => $request->get('code'),
|
|
]);
|
|
|
|
if ($tokenResponse->failed()) {
|
|
return redirect()->route('login')->with('error', 'SSO-Token konnte nicht abgerufen werden.');
|
|
}
|
|
|
|
$accessToken = $tokenResponse->json('access_token');
|
|
|
|
// Userinfo abrufen
|
|
$userInfo = Http::withToken($accessToken)->get($sso->userInfoUrl());
|
|
|
|
if ($userInfo->failed()) {
|
|
return redirect()->route('login')->with('error', 'SSO-Benutzerinformationen konnten nicht abgerufen werden.');
|
|
}
|
|
|
|
$info = $userInfo->json();
|
|
$providerId = $info['sub'] ?? null;
|
|
$email = $info['email'] ?? null;
|
|
$name = $info['name'] ?? $info['preferred_username'] ?? $email;
|
|
|
|
if (!$providerId || !$email) {
|
|
return redirect()->route('login')->with('error', 'Unvollständige Benutzerinformationen vom SSO-Provider.');
|
|
}
|
|
|
|
// User suchen oder anlegen
|
|
$user = User::where('sso_provider_id', $providerId)->first()
|
|
?? User::where('email', $email)->first();
|
|
|
|
if (!$user) {
|
|
$user = User::create([
|
|
'name' => $name,
|
|
'email' => $email,
|
|
'password' => bcrypt(Str::random(32)),
|
|
'sso_provider_id' => $providerId,
|
|
'is_sso_user' => true,
|
|
]);
|
|
} elseif (!$user->sso_provider_id) {
|
|
// Bestehenden Account mit SSO verknüpfen
|
|
$user->sso_provider_id = $providerId;
|
|
$user->is_sso_user = true;
|
|
$user->save();
|
|
}
|
|
|
|
Auth::login($user, true);
|
|
|
|
return redirect()->intended(route('user-nail-polishes.index'));
|
|
}
|
|
}
|