Anmeldung ueber Authentik und andere OIDC-Anbieter

Anmeldefluss mit PKCE (S256), State und Nonce. Das ID-Token wird gegen den
JWKS des Anbieters geprueft — Signatur, Aussteller, Empfaenger, Ablauf und
Nonce; alles Noetige kommt aus dem Discovery-Dokument, damit die Anbindung
nicht auf Authentik festgelegt ist.

Wiedererkannt wird jemand an der Kennung des Anbieters (sub), nicht an der
E-Mail-Adresse — Adressen aendern sich. Ein bestehendes Konto mit derselben
Adresse wird verknuepft statt verdoppelt, aber nur bei bestaetigter Adresse:
sonst koennte sich beim Anbieter jemand eine fremde Adresse eintragen und
damit ein hiesiges Konto uebernehmen.

Uebernommen wird bewusst nur, wer jemand ist — plus die Instanz-Administration,
wenn OIDC_ADMIN_GROUP gesetzt ist. Gruppen und Rollen bleiben Sache dieser
Anwendung: die Gruppen im Verzeichnis bedeuten etwas anderes als die hier.

Konten aus dem Verzeichnis haben kein Passwort mehr (passwordHash ist jetzt
optional); Konto-Bereich und Passwort-Anmeldung sagen das statt an einer
leeren Pruefung zu scheitern.

Anleitung mit allen Feldern in Authentik: docs/authentik.md
This commit is contained in:
Florian Wilde
2026-08-15 00:12:21 +02:00
parent e296b1e6c2
commit c8d60d8006
14 changed files with 1039 additions and 11 deletions
+21 -1
View File
@@ -7,6 +7,7 @@ import { isAuthEnabled } from "@/lib/auth/actor";
import { requirePageActor } from "@/lib/auth/guard";
import { MIN_PASSWORD_LENGTH } from "@/lib/auth/password";
import { ROLE_DESCRIPTIONS, ROLE_LABELS } from "@/lib/auth/policy";
import { prisma } from "@/lib/db";
export const metadata: Metadata = { title: "Konto" };
export const dynamic = "force-dynamic";
@@ -14,6 +15,15 @@ export const dynamic = "force-dynamic";
export default async function AccountPage() {
const actor = await requirePageActor();
// Konten aus dem externen Verzeichnis haben hier kein Passwort.
const ausVerzeichnis =
actor.kind === "user"
? ((await prisma.user.findUnique({
where: { id: actor.user.id },
select: { passwordHash: true },
}))?.passwordHash ?? null) === null
: false;
// Bei ausgeschalteter Anmeldung gibt es kein Konto — dann erklärt die Seite,
// wie sich das umstellen lässt, statt einfach leer zu sein.
if (actor.kind !== "user") {
@@ -123,7 +133,17 @@ export default async function AccountPage() {
{isAuthEnabled() ? (
<>
<ChangePasswordForm minLength={MIN_PASSWORD_LENGTH} />
{ausVerzeichnis ? (
<Card>
<CardTitle>Passwort</CardTitle>
<p className="mt-2 text-sm text-slate-600">
Dieses Konto meldet sich über den externen Anbieter an. Ein
Passwort gibt es hier nicht — geändert wird es beim Anbieter.
</p>
</Card>
) : (
<ChangePasswordForm minLength={MIN_PASSWORD_LENGTH} />
)}
<SignOutCard />
</>
) : null}