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>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c5fe3ad808
commit
60259a3655
@@ -0,0 +1,222 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'SSO-Konfiguration – NeoNail DB')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="page-hero d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
|
||||
<div>
|
||||
<h1><i class="fas fa-shield-alt me-2"></i>Single Sign-On</h1>
|
||||
<p>Authentik OAuth2/OIDC Konfiguration</p>
|
||||
</div>
|
||||
<span class="count-chip">
|
||||
@if($sso->isConfigured())
|
||||
<i class="fas fa-circle text-success" style="font-size:.6rem"></i> Aktiv
|
||||
@else
|
||||
<i class="fas fa-circle text-danger" style="font-size:.6rem"></i> Nicht konfiguriert
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Status-Banner --}}
|
||||
@if($sso->isConfigured())
|
||||
<div class="alert alert-success mb-4">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
SSO ist aktiv. Auf der Login-Seite wird der <strong>„Mit Authentik anmelden"</strong>-Button angezeigt.
|
||||
</div>
|
||||
@elseif($sso->enabled && !$sso->isConfigured())
|
||||
<div class="alert alert-warning mb-4">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
SSO ist aktiviert, aber die Konfiguration ist unvollständig. Bitte alle Felder ausfüllen.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
{{-- Konfigurationsformular --}}
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-cog me-2"></i>Einstellungen
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="POST" action="{{ route('admin.sso.update') }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
{{-- Aktivieren --}}
|
||||
<div class="mb-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
name="enabled" id="enabled" value="1"
|
||||
{{ $sso->enabled ? 'checked' : '' }}>
|
||||
<label class="form-check-label fw-semibold" for="enabled">
|
||||
SSO aktivieren
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text mt-1">
|
||||
Nur wenn aktiviert <em>und</em> alle Felder ausgefüllt sind, erscheint der SSO-Button auf der Login-Seite.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
{{-- Base URL --}}
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="base_url">
|
||||
Authentik Base URL <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="url" class="form-control @error('base_url') is-invalid @enderror"
|
||||
id="base_url" name="base_url"
|
||||
value="{{ old('base_url', $sso->base_url) }}"
|
||||
placeholder="https://authentik.example.com">
|
||||
@error('base_url')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">Die URL deiner Authentik-Instanz, ohne abschließenden Slash.</div>
|
||||
</div>
|
||||
|
||||
{{-- Client ID --}}
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="client_id">
|
||||
Client ID <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="text" class="form-control @error('client_id') is-invalid @enderror"
|
||||
id="client_id" name="client_id"
|
||||
value="{{ old('client_id', $sso->client_id) }}"
|
||||
placeholder="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
autocomplete="off">
|
||||
@error('client_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- Client Secret --}}
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="client_secret">
|
||||
Client Secret <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control @error('client_secret') is-invalid @enderror"
|
||||
id="client_secret" name="client_secret"
|
||||
value="{{ old('client_secret', $sso->client_secret) }}"
|
||||
placeholder="{{ $sso->client_secret ? '••••••••••••••••' : 'Client Secret eingeben' }}"
|
||||
autocomplete="off">
|
||||
<button class="btn btn-outline-secondary" type="button"
|
||||
onclick="toggleSecret()" id="toggleSecretBtn">
|
||||
<i class="fas fa-eye" id="toggleSecretIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
@error('client_secret')
|
||||
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- Slug --}}
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="slug">
|
||||
Application Slug
|
||||
<span class="badge bg-secondary ms-1" style="font-size:.65rem">optional</span>
|
||||
</label>
|
||||
<input type="text" class="form-control @error('slug') is-invalid @enderror"
|
||||
id="slug" name="slug"
|
||||
value="{{ old('slug', $sso->slug) }}"
|
||||
placeholder="neonail-db">
|
||||
@error('slug')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">Nur zur Dokumentation – wird nicht im OAuth2-Flow verwendet.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>Speichern
|
||||
</button>
|
||||
@if($sso->isConfigured())
|
||||
<a href="{{ route('sso.redirect') }}" target="_blank" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-external-link-alt me-2"></i>SSO testen
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Info-Sidebar --}}
|
||||
<div class="col-lg-4">
|
||||
|
||||
{{-- Callback URL --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-link me-2"></i>Redirect URI
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<p style="font-size:.82rem; color:#6b7280; margin-bottom:8px;">
|
||||
Diese URL musst du in Authentik als <strong>Redirect URI</strong> eintragen:
|
||||
</p>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<code style="font-size:.78rem; background:#f3f4f6; padding:8px 10px; border-radius:8px; flex:1; word-break:break-all;">
|
||||
{{ route('sso.callback') }}
|
||||
</code>
|
||||
<button class="btn btn-sm btn-outline-secondary flex-shrink-0"
|
||||
onclick="copyRedirectUri()" title="Kopieren">
|
||||
<i class="fas fa-copy" id="copyIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Setup-Anleitung --}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-book me-2"></i>Setup in Authentik
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<ol style="font-size:.83rem; color:#374151; padding-left:1.2rem; margin:0; line-height:1.8;">
|
||||
<li>In Authentik: <strong>Applications → Create</strong></li>
|
||||
<li>Provider-Typ: <strong>OAuth2/OpenID Provider</strong></li>
|
||||
<li>Authorization flow wählen</li>
|
||||
<li>Redirect URI eintragen (siehe links)</li>
|
||||
<li><strong>Client ID</strong> und <strong>Client Secret</strong> kopieren</li>
|
||||
<li>Hier eintragen und speichern</li>
|
||||
</ol>
|
||||
|
||||
<hr style="margin: 12px 0;">
|
||||
|
||||
<p style="font-size:.78rem; color:#9ca3af; margin:0;">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
Scopes: <code>openid email profile</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
function toggleSecret() {
|
||||
const input = document.getElementById('client_secret');
|
||||
const icon = document.getElementById('toggleSecretIcon');
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.classList.replace('fa-eye', 'fa-eye-slash');
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
function copyRedirectUri() {
|
||||
const uri = @json(route('sso.callback'));
|
||||
const icon = document.getElementById('copyIcon');
|
||||
navigator.clipboard.writeText(uri).then(() => {
|
||||
icon.classList.replace('fa-copy', 'fa-check');
|
||||
setTimeout(() => icon.classList.replace('fa-check', 'fa-copy'), 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -74,6 +74,21 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@php $sso = \App\Models\SsoSetting::get(); @endphp
|
||||
@if($sso->isConfigured())
|
||||
<div class="d-flex align-items-center my-4" style="gap:12px;">
|
||||
<hr style="flex:1; border-color:rgba(0,0,0,.12); margin:0;">
|
||||
<span style="font-size:.78rem; color:#9ca3af; white-space:nowrap;">oder</span>
|
||||
<hr style="flex:1; border-color:rgba(0,0,0,.12); margin:0;">
|
||||
</div>
|
||||
<a href="{{ route('sso.redirect') }}" class="btn btn-outline-secondary w-100 btn-lg d-flex align-items-center justify-content-center gap-2">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20A14.5 14.5 0 0 0 12 2"/><path d="M2 12h20"/>
|
||||
</svg>
|
||||
Mit Authentik anmelden
|
||||
</a>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -471,6 +471,9 @@
|
||||
<i class="fas fa-palette me-2"></i>Nagellacke</a></li>
|
||||
<li><a class="dropdown-item" href="{{ route('admin.statistics') }}">
|
||||
<i class="fas fa-chart-bar me-2"></i>Statistiken</a></li>
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.1);"></li>
|
||||
<li><a class="dropdown-item" href="{{ route('admin.sso.show') }}">
|
||||
<i class="fas fa-shield-alt me-2"></i>SSO-Konfiguration</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user