# Vue + Zitadel — Lernprojekt Kleine Vue-App, die Login/Logout über Zitadel (OIDC) zeigt — ohne eigenes Passwort-Formular. **Stack:** Vue 3, TypeScript, Vite **Zitadel-Instanz:** `https://auth.schild.work` **Kernkomponente:** `src/components/auth-area.vue` ```bash pnpm install pnpm dev ``` --- ## Wie funktioniert der Login? Die App nutzt **OpenID Connect (OIDC)** mit **Authorization Code + PKCE** — der Standard für SPAs. ``` Vue App Zitadel │ 1. Sign in/up │ │ ─────────────────────►│ Login- oder Register-Seite │ 2. Redirect mit code │ │ ◄─────────────────────│ ?code=... auf localhost:5173 │ 3. POST /oauth/v2/token│ │ ─────────────────────►│ code + code_verifier (PKCE) │ 4. access_token │ │ ◄─────────────────────│ (+ id_token, optional refresh_token) ``` **Sign in** → normale Login-Seite von Zitadel. **Sign up** → gleicher Flow, aber mit `prompt=create` (direkt Registrierung). Credentials (E-Mail/Passwort) werden **nur auf Zitadel** eingegeben, nie in der Vue-App. **Logout** → Redirect zu Zitadel `end_session`, danach zurück auf die App. --- ## Wie ist das implementiert? Alles in `auth-area.vue`: | Schritt | Was passiert | |---------|----------------| | Sign in / Sign up | `redirectToAuthorize()` — PKCE-Verifier erzeugen, Redirect zu `/oauth/v2/authorize` | | Zurück auf der App | `handleCallback()` liest `?code=` aus der URL | | Token holen | `exchangeCodeForTokens()` — POST an `/oauth/v2/token` | | Session speichern | `access_token` und `id_token` in `sessionStorage` | | App-Start | `loadStoredSession()` — Token aus `sessionStorage` laden | Konfiguration oben in der Datei: ```ts const ZITADEL_ISSUER = "https://auth.schild.work"; const CLIENT_ID = "…"; // aus Zitadel Console const REDIRECT_URI = "http://localhost:5173/"; const POST_LOGOUT_REDIRECT_URI = "http://localhost:5173/"; const SCOPES = "openid profile email offline_access"; ``` In Zitadel: Application (User Agent / SPA, PKCE), Redirect URIs und Post Logout Redirect URI eintragen, **Register allowed** für Sign up aktivieren. --- ## Woran erkennt man, dass man eingeloggt ist? **In der App:** `isAuthenticated` — ein `computed`, das prüft ob `accessToken` gesetzt ist. ```ts const isAuthenticated = computed(() => accessToken.value !== null); ``` Im Template: eingeloggt → Name/E-Mail + Logout; nicht eingeloggt → Sign in / Sign up. **User-Anzeige:** Der `id_token` ist ein JWT. Payload decodieren (Mitte zwischen den Punkten) liefert z. B. `email`, `name` — siehe `userLabel` in der Komponente. **Technisch:** Ein gültiger `access_token` in `sessionStorage` (`zitadel_access_token`) bedeutet: Session vorhanden. Nach Tab-Schließen weg (`sessionStorage`), nach erneutem Öffnen neu einloggen. --- ## API-Aufrufe mit Login Geschützte APIs erwarten den Token im Header: ```ts const token = sessionStorage.getItem("zitadel_access_token"); const response = await fetch("https://api.example.com/me", { headers: { Authorization: `Bearer ${token}`, }, }); ``` Die **API** (nicht Vue) muss das JWT prüfen — Signatur gegen Zitadel JWKS (`/oauth/v2/keys`), Issuer `https://auth.schild.work`, Ablaufzeit (`exp`). | Token | Zweck | |-------|--------| | `access_token` | API-Aufrufe (`Authorization: Bearer …`) | | `id_token` | User-Infos in der UI (JWT decodieren) | | `refresh_token` | Neues `access_token` ohne erneuten Login (Scope `offline_access`) | **Wichtig:** Token nur an **eigene, vertrauenswürdige APIs** senden. Die API validiert das Token serverseitig — die Vue-App allein „macht“ keine Sicherheit. --- ## Was dieses Projekt nicht macht - Kein eigenes Registrierungsformular (E-Mail/Passwort direkt an Zitadel) — dafür bräuchte man ein Backend mit Service-User-Token. - Kein Refresh-Token-Handling — könnte später ergänzt werden.