Files
neonail-database/app/Http/Controllers/Auth/SsoController.php
T
HousemannandClaude Sonnet 4.6 60259a3655 Add Authentik SSO via OAuth2/OIDC
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>
2026-06-13 15:13:30 +02:00

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'));
}
}