# Auth: Login-Patterns für deinen Prototyp

Auth ist der häufigste Grund, warum Gründer nach acht Stunden keinen Prototyp haben. Sie verkämpfen sich an eigenem Magic-Link, eigenem OAuth-Setup, eigener Passwort-Reset-Logik. Dabei gilt: im Prototyp brauchst du oft gar kein Login. Und wenn doch, dann das einfachste, das du finden kannst.

Im Workshop-Stack ist das einfachste Supabase Auth. Es ist eingebaut, fertig getestet, und du tippst keine 50 Zeilen Code dafür.

## Erste Frage: brauchst du überhaupt Login?

Frag dich ehrlich, ob dein Prototyp wirklich Login braucht. Zwei Beispiele.

**Landing-Page mit Warteliste:** kein Login. Du brauchst ein Formular, das eine Email in eine Tabelle schreibt. Das ist alles.

**Tool, das eine KI-Antwort liefert:** wahrscheinlich kein Login. Lass die Leute zwei, drei Anfragen kostenlos machen, danach Email-Wall. Bis dahin sehen sie schon den Wert.

Login brauchst du, sobald Nutzer Daten anlegen, die ihnen gehören und die sie wiedersehen wollen. Dann aber sofort, sonst baust du später alles um.

## Workshop-Stack: Supabase Auth

Supabase Auth bietet drei Wege zum Login, die für 99 Prozent aller Prototypen reichen.

1. **Magic-Link per Email.** Nutzer gibt Email ein, klickt auf Link im Posteingang, ist eingeloggt. Kein Passwort, keine Reset-Mails, kein Stress.
2. **OAuth über Google, GitHub, Apple, Discord.** Ein Klick, Nutzer ist drin. Ideal für technisch versierte Zielgruppen.
3. **Passwort-Login** (klassisch Email + Passwort). Funktioniert, aber empfiehlt sich nur wenn du gute Gründe hast.

Empfehlung für den Workshop: starte mit Magic-Link. Add OAuth dazu, wenn deine Zielgruppe es erwartet.

### Setup in Supabase

1. In Supabase ein neues Projekt anlegen (kostenlos).
2. Unter "Authentication" -> "Providers" siehst du alle Login-Methoden. Email ist standardmäßig an.
3. Für Google-Login: unter "Providers" -> "Google" aktivieren, du brauchst eine Google-Cloud-Console und Client-ID. Dauert 15 Minuten.
4. Unter "Project Settings" -> "API" findest du die zwei Werte, die du brauchst: `Project URL` und `anon public key`. Beide kommen in dein Next.js-Projekt.

### Einbinden in Next.js

In deinem Projekt eine Datei `.env.local` anlegen:

```
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJI...
```

Dann eine Datei `lib/supabase.ts`:

```typescript
import { createBrowserClient } from "@supabase/ssr";

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  );
}
```

Login-Form als React-Component:

```typescript
"use client";
import { createClient } from "@/lib/supabase";
import { useState } from "react";

export default function Login() {
  const [email, setEmail] = useState("");
  const supabase = createClient();

  async function signIn() {
    const { error } = await supabase.auth.signInWithOtp({
      email,
      options: { emailRedirectTo: `${location.origin}/auth/callback` },
    });
    if (error) alert(error.message);
    else alert("Check deine Mails.");
  }

  return (
    <div className="p-4">
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="deine@email.de"
        className="border p-2 rounded"
      />
      <button onClick={signIn} className="bg-blue-500 text-white p-2 rounded">
        Login per Email
      </button>
    </div>
  );
}
```

OAuth ist ein Einzeiler:

```typescript
await supabase.auth.signInWithOAuth({ provider: "google" });
```

Den eingeloggten Nutzer kriegst du jederzeit so:

```typescript
const { data: { user } } = await supabase.auth.getUser();
```

Logout:

```typescript
await supabase.auth.signOut();
```

Das ist alles. Kein Token-Refresh, keine Cookies selbst setzen. Supabase macht das im Hintergrund.

## Row Level Security: das Sicherheitskonzept verstehen

Wichtig zu wissen: Supabase nutzt PostgreSQL und exposed deine Datenbank über eine API. Wenn du keine Regeln definierst, könnte theoretisch jeder eingeloggte Nutzer jede Zeile in jeder Tabelle lesen.

Die Lösung heißt **Row Level Security** (RLS). Du definierst pro Tabelle, welche Zeilen ein Nutzer sehen darf. Beispiel: jeder Nutzer darf nur seine eigenen Notizen sehen.

In Supabase unter "Authentication" -> "Policies" die Tabelle auswählen, "Enable RLS" anklicken, dann eine Policy anlegen:

```sql
CREATE POLICY "Eigene Notizen lesen"
ON notes FOR SELECT
USING (auth.uid() = user_id);

CREATE POLICY "Eigene Notizen erstellen"
ON notes FOR INSERT
WITH CHECK (auth.uid() = user_id);
```

Damit darf ein Nutzer nur Zeilen sehen oder anlegen, bei denen `user_id` seiner eigenen ID entspricht.

**Wichtig: RLS aktivieren ist Pflicht.** Wenn du es vergisst, ist deine Datenbank offen. Supabase warnt dich im Dashboard, aber du musst es selbst aktivieren.

## Production-Stack: eigene Cookie-Session

Später, im Production-Stack, sieht Auth komplett anders aus. Ben Scheurer baut eigene Sessions, weil er volle Kontrolle über den Login-Flow will (eigene Onboarding-Logik, Custom-Permissions, kein Vendor-Lock-in).

So funktioniert es kurz:

- Passwörter werden mit **Argon2id** gehasht (modernster Password-Hash, gewinnt regelmäßig Crypto-Wettbewerbe). Niemals MD5, SHA1 oder reines bcrypt für neue Projekte.
- Beim Login wird ein **JWT** erstellt und in ein **HttpOnly-Cookie** geschrieben. HttpOnly heißt: JavaScript im Browser kann das Cookie nicht lesen, schützt vor XSS-Angriffen.
- Das Cookie trägt der Browser bei jedem Request automatisch mit. Der Server prüft den JWT, weiß wer eingeloggt ist.
- Logout = Cookie löschen.

Code dafür ist ein einmaliges Setup von ein paar hundert Zeilen. Danach ist Auth eine Nicht-Sache.

**Migration von Supabase Auth dorthin:** Nutzer-Tabelle übernehmen, alle Nutzer einmal "Passwort zurücksetzen" triggern. Das ist die sauberste Migration, weil die Password-Hashes in Supabase anders sind als deine eigenen.

## Was du heute nicht brauchst

- **Eigenes Magic-Link-System:** Supabase macht es, eigene Mail-Templates kannst du im Dashboard anpassen.
- **Two-Factor Authentication:** Supabase Pro bietet es, aber für Prototyp überflüssig.
- **SAML, SSO, Enterprise-Login:** kommt erst, wenn du B2B-Enterprise verkaufst.
- **Email-Bestätigung deaktivieren für Tests:** ja, das geht in Supabase, lass es im Dev-Projekt an, sonst gewöhnst du dir unsicheres Verhalten an.

## Server-seitig den Login prüfen

In Next.js mit Server Actions willst du auf dem Server wissen, wer eingeloggt ist, bevor du Daten zurückgibst oder schreibst. Dafür gibt es einen Server-Client von Supabase, der das Auth-Cookie liest. Eine typische Server Action sieht so aus:

```typescript
"use server";
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function meineNotizenLaden() {
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: { get: (n) => cookies().get(n)?.value } }
  );
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) throw new Error("Nicht eingeloggt");
  const { data } = await supabase
    .from("notes")
    .select("*")
    .eq("user_id", user.id);
  return data;
}
```

Wichtig: durch die RLS-Policy oben kannst du nicht versehentlich fremde Notizen ausliefern. Selbst wenn du `eq("user_id", ...)` vergisst, schiebt PostgreSQL dem Riegel vor. Defense in Depth.

## Häufige Stolperfallen

**Redirect-URL nicht gesetzt.** Nach dem Klick auf den Magic-Link landet der Nutzer auf einer Callback-URL. Diese URL muss in Supabase unter "Authentication" -> "URL Configuration" eingetragen sein, sonst Fehler.

**Session-Cookie geht nicht in Server Actions an.** Lösung: das `@supabase/ssr`-Paket benutzen, nicht das alte `@supabase/auth-helpers-nextjs`.

**Email landet im Spam.** Supabase verschickt aus einer generischen Domain. Für ernsthafte Produkte: eigenen SMTP-Provider (Resend, Postmark) in Supabase einhängen. Das ist auch Free-Tier-fähig.

## Nächster Schritt

Leg in deinem Supabase-Projekt eine Test-Tabelle an, aktiviere RLS, baue die Login-Component oben ein und teste den Flow. Wenn das läuft, hast du das schwerste am Prototyp schon hinter dir. Danach in `webapp-deploy.md` weiter zum Live-Schalten.
